├── .github ├── .gitignore ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.md │ └── bug-report.md ├── CONTRIBUTING.md ├── workflows │ ├── R-CMD-check.yaml │ ├── pkgdown.yaml │ └── bookdown.yaml └── CODE_OF_CONDUCT.md ├── pkgdown ├── assets │ ├── ace-1.2.3 │ │ ├── mode-text.js │ │ ├── mode-plain_text.js │ │ ├── theme-textmate.js │ │ ├── mode-rdoc.js │ │ ├── mode-r.js │ │ └── mode-xml.js │ └── snippets │ │ └── snippets.js ├── favicon │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ └── apple-touch-icon-180x180.png └── templates │ └── in-header.html ├── vignettes ├── articles │ ├── images │ │ └── .gitignore │ ├── examples.yml │ └── examples.Rmd └── blogdown.Rmd ├── docs ├── latex │ ├── after_body.tex │ ├── before_body.tex │ └── preamble.tex ├── images │ ├── logo.png │ ├── caution.png │ ├── cover.png │ ├── new-post.png │ ├── addin-serve.png │ ├── dedication.pdf │ ├── edit-content.png │ ├── github-edit.png │ ├── insert-image.png │ ├── jtleek-tweet.png │ ├── new-project.png │ ├── serve-site.png │ ├── update-meta.png │ ├── cloudflare-dns.png │ ├── lithium-theme.png │ ├── blogdown-project.png │ ├── chrome-devtools.png │ ├── folder-structure.png │ ├── netlify-settings.png │ ├── overwrite-image.png │ └── project-options.png ├── .gitignore ├── _bookdown.yml ├── Makefile ├── 11-references.Rmd ├── book.bib ├── _render.R ├── README.md ├── WIP.html ├── _output.yml ├── css │ └── style.css ├── 10-experience.Rmd ├── 06-r-markdown.Rmd ├── 00-author.Rmd ├── 08-domain-name.Rmd └── 04-migration.Rmd ├── tests ├── test-cran.R ├── test-ci.R ├── test-cran │ ├── test-install.R │ ├── test-list.R │ ├── test-render.R │ ├── test-addin.R │ ├── test-clean.R │ └── test-utils.R └── test-ci │ └── test-themes.R ├── .gitignore ├── man ├── figures │ ├── logo.png │ └── logo.svg ├── edit_draft.Rd ├── config_vercel.Rd ├── config_Rprofile.Rd ├── blogdown.Rd ├── build_dir.Rd ├── clean_duplicates.Rd ├── dep_path.Rd ├── hugo_installers.Rd ├── find_yaml.Rd ├── bundle_site.Rd ├── install_theme.Rd ├── config_netlify.Rd ├── filter_newfile.Rd ├── html_page.Rd ├── find_hugo.Rd ├── read_toml.Rd ├── serve_site.Rd ├── check_site.Rd ├── shortcode.Rd ├── install_hugo.Rd ├── build_site.Rd └── hugo_cmd.Rd ├── inst ├── rstudio │ ├── templates │ │ └── project │ │ │ ├── hugo-logo.png │ │ │ └── skeleton.dcf │ └── addins.dcf ├── resources │ ├── postref.html │ ├── template-minimal.html │ ├── Rprofile │ └── 2020-12-01-r-rmarkdown.Rmd ├── NEWS.Rd ├── CITATION └── scripts │ ├── update_meta.R │ ├── new_post.R │ └── insert_image.R ├── .Rbuildignore ├── codecov.yml ├── blogdown.Rproj ├── NAMESPACE ├── R ├── clean.R ├── package.R ├── addin.R ├── site.R └── format.R ├── DESCRIPTION ├── _pkgdown.yml └── README.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /pkgdown/assets/ace-1.2.3/mode-text.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vignettes/articles/images/.gitignore: -------------------------------------------------------------------------------- 1 | *.png 2 | -------------------------------------------------------------------------------- /docs/latex/after_body.tex: -------------------------------------------------------------------------------- 1 | \backmatter 2 | \printindex 3 | -------------------------------------------------------------------------------- /tests/test-cran.R: -------------------------------------------------------------------------------- 1 | library(testit) 2 | test_pkg('blogdown', 'test-cran') 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | reference 6 | -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/logo.png -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/man/figures/logo.png -------------------------------------------------------------------------------- /docs/images/caution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/caution.png -------------------------------------------------------------------------------- /docs/images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/cover.png -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _bookdown_files 2 | _book 3 | packages.bib 4 | images/dedication.lyx 5 | rsconnect 6 | -------------------------------------------------------------------------------- /docs/images/new-post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/new-post.png -------------------------------------------------------------------------------- /docs/images/addin-serve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/addin-serve.png -------------------------------------------------------------------------------- /docs/images/dedication.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/dedication.pdf -------------------------------------------------------------------------------- /docs/images/edit-content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/edit-content.png -------------------------------------------------------------------------------- /docs/images/github-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/github-edit.png -------------------------------------------------------------------------------- /docs/images/insert-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/insert-image.png -------------------------------------------------------------------------------- /docs/images/jtleek-tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/jtleek-tweet.png -------------------------------------------------------------------------------- /docs/images/new-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/new-project.png -------------------------------------------------------------------------------- /docs/images/serve-site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/serve-site.png -------------------------------------------------------------------------------- /docs/images/update-meta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/update-meta.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /docs/images/cloudflare-dns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/cloudflare-dns.png -------------------------------------------------------------------------------- /docs/images/lithium-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/lithium-theme.png -------------------------------------------------------------------------------- /docs/images/blogdown-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/blogdown-project.png -------------------------------------------------------------------------------- /docs/images/chrome-devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/chrome-devtools.png -------------------------------------------------------------------------------- /docs/images/folder-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/folder-structure.png -------------------------------------------------------------------------------- /docs/images/netlify-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/netlify-settings.png -------------------------------------------------------------------------------- /docs/images/overwrite-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/overwrite-image.png -------------------------------------------------------------------------------- /docs/images/project-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/docs/images/project-options.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /inst/rstudio/templates/project/hugo-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/inst/rstudio/templates/project/hugo-logo.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/blogdown/HEAD/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /inst/resources/postref.html: -------------------------------------------------------------------------------- 1 | {{ if eq (getenv "HUGO_BLOGDOWN_POST_RELREF") "true" }}{{ .Page.RelPermalink }}{{ else }}{{ .Page.Permalink }}{{ end }} -------------------------------------------------------------------------------- /tests/test-ci.R: -------------------------------------------------------------------------------- 1 | # run tests on CI (these tests will install Hugo and themes) 2 | if (!is.na(Sys.getenv('CI', NA))) testit::test_pkg('blogdown', 'test-ci') 3 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.github$ 4 | ^docs$ 5 | ^codecov\.yml$ 6 | ^NEWS\.md$ 7 | ^README\.md$ 8 | ^_pkgdown\.yml$ 9 | ^pkgdown$ 10 | ^vignettes$ 11 | ^reference$ 12 | -------------------------------------------------------------------------------- /docs/_bookdown.yml: -------------------------------------------------------------------------------- 1 | book_filename: blogdown 2 | repo: https://github.com/rstudio/blogdown/ 3 | output_dir: _book 4 | clean: [packages.bib, blogdown.bbl] 5 | language: 6 | label: 7 | fig: "FIGURE " 8 | tab: "TABLE " 9 | ui: 10 | chapter_name: "Chapter " 11 | delete_merged_file: true 12 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /tests/test-cran/test-install.R: -------------------------------------------------------------------------------- 1 | library(testit) 2 | 3 | assert('is_version can detect hugo version number', { 4 | (is_version('1.1.2.3')) 5 | (is_version('2.1.2')) 6 | (is_version('10.1')) 7 | (is_version('12')) 8 | (!is_version('1.A')) 9 | (!is_version('Some text')) 10 | (!is_version('Some/path')) 11 | }) 12 | -------------------------------------------------------------------------------- /inst/NEWS.Rd: -------------------------------------------------------------------------------- 1 | \name{NEWS} 2 | \title{News for Package 'blogdown'} 3 | 4 | \section{CHANGES IN blogdown VERSION 999.999}{ 5 | \itemize{ 6 | \item This NEWS file is only a placeholder. The version 999.999 does not really 7 | exist. Please read the NEWS on Github: \url{https://github.com/rstudio/blogdown/releases} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /docs/latex/before_body.tex: -------------------------------------------------------------------------------- 1 | %\cleardoublepage\newpage\thispagestyle{empty}\null 2 | %\cleardoublepage\newpage\thispagestyle{empty}\null 3 | %\cleardoublepage\newpage 4 | \thispagestyle{empty} 5 | \begin{center} 6 | \includegraphics{images/dedication.pdf} 7 | \end{center} 8 | 9 | \setlength{\abovedisplayskip}{-5pt} 10 | \setlength{\abovedisplayshortskip}{-5pt} 11 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | BOOKDOWN_FULL_PDF=false Rscript --quiet _render.R 3 | 4 | pdf: 5 | Rscript --quiet _render.R "bookdown::pdf_book" &&\ 6 | mv _book/blogdown.pdf _book/blogdown-full.pdf 7 | 8 | pdf2: 9 | BOOKDOWN_FULL_PDF=false Rscript --quiet _render.R "bookdown::pdf_book" 10 | 11 | gitbook: 12 | Rscript --quiet _render.R "bookdown::gitbook" 13 | 14 | clean: 15 | Rscript --quiet -e "bookdown::clean_book(TRUE)" 16 | -------------------------------------------------------------------------------- /docs/11-references.Rmd: -------------------------------------------------------------------------------- 1 | `r if (knitr::is_html_output()) ' 2 | # References {-} 3 | '` 4 | 5 | ```{r include=FALSE} 6 | bib = knitr::write_bib(c( 7 | .packages(), 'blogdown', 'bookdown', 'knitr', 'rmarkdown', 'htmlwidgets', 'webshot', 'servr', 'xaringan', 'animation', 'processx', 'later', 'widgetframe', 'pkgdown' 8 | ), file = NULL) 9 | bib = unlist(bib) 10 | bib = gsub("'(Htmlwidgets|iframes)'", '\\1', bib) 11 | xfun::write_utf8(bib, 'packages.bib') 12 | ``` 13 | -------------------------------------------------------------------------------- /tests/test-cran/test-list.R: -------------------------------------------------------------------------------- 1 | library(testit) 2 | 3 | assert('list_rmds() ignores thing in renv / packrat folder', { 4 | 5 | dir = tempfile('testit-dir-') 6 | dir.create(dir, recursive = TRUE) 7 | 8 | dir.create(file.path(dir, 'renv')) 9 | file.create(rmd_renv <- file.path(dir, 'renv/ignore.Rmd')) 10 | 11 | rmd = list_rmds(dir) 12 | (rmd %==% character()) 13 | rmd = list_rmds(files = rmd_renv) 14 | (rmd %==% character()) 15 | 16 | unlink(dir, recursive = TRUE) 17 | 18 | }) 19 | -------------------------------------------------------------------------------- /man/edit_draft.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/check.R 3 | \name{edit_draft} 4 | \alias{edit_draft} 5 | \title{Open a list of draft posts} 6 | \usage{ 7 | edit_draft(files) 8 | } 9 | \arguments{ 10 | \item{files}{A vector of file paths.} 11 | } 12 | \description{ 13 | If a file is opened in RStudio, this function will try to locate the 14 | \code{draft} field in YAML automatically, so you can edit this field 15 | immediately. 16 | } 17 | \keyword{internal} 18 | -------------------------------------------------------------------------------- /pkgdown/assets/ace-1.2.3/mode-plain_text.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/mode/plain_text",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/text_highlight_rules","ace/mode/behaviour"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./text_highlight_rules").TextHighlightRules,o=e("./behaviour").Behaviour,u=function(){this.HighlightRules=s,this.$behaviour=new o};r.inherits(u,i),function(){this.type="text",this.getNextLineIndent=function(e,t,n){return""},this.$id="ace/mode/plain_text"}.call(u.prototype),t.Mode=u}) -------------------------------------------------------------------------------- /man/config_vercel.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{config_vercel} 4 | \alias{config_vercel} 5 | \title{Create the configuration file for Vercel} 6 | \usage{ 7 | config_vercel(output = "vercel.json") 8 | } 9 | \arguments{ 10 | \item{output}{Path to the output file, or \code{NULL} to print the config.} 11 | } 12 | \description{ 13 | Create \file{vercel.json} that contains the Hugo version currently used. 14 | } 15 | \references{ 16 | Vercel: \url{https://vercel.com} 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Issue guide 4 | url: https://yihui.org/issue/ 5 | about: First time here or need a refresh ? Please consult the issue guide before posting. 6 | - name: Ask a question on RStudio Community 7 | url: https://community.rstudio.com/tags/c/R-Markdown/10/blogdown 8 | about: Please ask and answer questions here. 9 | - name: Ask a question on Stack Overflow 10 | url: https://stackoverflow.com/questions/tagged/r+blogdown 11 | about: Please ask and answer questions here. 12 | -------------------------------------------------------------------------------- /inst/resources/template-minimal.html: -------------------------------------------------------------------------------- 1 | $for(header-includes)$ 2 | $header-includes$ 3 | $endfor$ 4 | $if(highlighting-css)$ 5 | 8 | $endif$ 9 | $for(css)$ 10 | 11 | $endfor$ 12 | 13 | $for(include-before)$ 14 | $include-before$ 15 | $endfor$ 16 | $if(toc)$ 17 |
18 | $if(toc-title)$ 19 |

$toc-title$

20 | $endif$ 21 | $table-of-contents$ 22 |
23 | $endif$ 24 | 25 | $body$ 26 | 27 | $for(include-after)$ 28 | $include-after$ 29 | $endfor$ 30 | -------------------------------------------------------------------------------- /blogdown.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | ProjectId: 7de02ffc-a200-4611-bc07-18b265ee1be3 3 | 4 | RestoreWorkspace: Default 5 | SaveWorkspace: Default 6 | AlwaysSaveHistory: Default 7 | 8 | EnableCodeIndexing: Yes 9 | UseSpacesForTab: Yes 10 | NumSpacesForTab: 2 11 | Encoding: UTF-8 12 | 13 | RnwWeave: knitr 14 | LaTeX: XeLaTeX 15 | 16 | AutoAppendNewline: Yes 17 | StripTrailingWhitespace: Yes 18 | 19 | BuildType: Package 20 | PackageInstallArgs: -v && Rscript -e "Rd2roxygen::rab(build=F,install=T)" 21 | PackageBuildArgs: -v && Rscript -e "Rd2roxygen::rab(build=T,install=F)" 22 | PackageCheckArgs: --as-cran 23 | -------------------------------------------------------------------------------- /tests/test-cran/test-render.R: -------------------------------------------------------------------------------- 1 | library(testit) 2 | 3 | assert('run_pandoc() find when Pandoc needs to convert', { 4 | (run_pandoc(c("nothing special", "requiring render")) %==% FALSE) 5 | (run_pandoc(c("```{=html}", "using raw block content", "```")) %==% TRUE) 6 | (run_pandoc(c("With inline raw `this works too`{=html}")) %==% TRUE) 7 | (run_pandoc(c("Using bib", "references:")) %==% TRUE) 8 | (run_pandoc(c("Using bib", "bibliography: test.bib")) %==% TRUE) 9 | opts = options(blogdown.markdown.format = "gfm") 10 | (run_pandoc(c("nothing special", "but option is set")) %==% TRUE) 11 | options(opts) 12 | }) 13 | 14 | -------------------------------------------------------------------------------- /docs/book.bib: -------------------------------------------------------------------------------- 1 | @Book{xie2015, 2 | title = {Dynamic Documents with {R} and knitr}, 3 | author = {Yihui Xie}, 4 | publisher = {Chapman and Hall/CRC}, 5 | address = {Boca Raton, Florida}, 6 | year = {2015}, 7 | edition = {2nd}, 8 | note = {ISBN 978-1498716963}, 9 | url = {http://yihui.org/knitr/}, 10 | } 11 | @Book{xie2016, 12 | title = {bookdown: Authoring Books and Technical Documents with {R} Markdown}, 13 | author = {Yihui Xie}, 14 | publisher = {Chapman and Hall/CRC}, 15 | address = {Boca Raton, Florida}, 16 | year = {2016}, 17 | note = {ISBN 978-1138700109}, 18 | url = {https://github.com/rstudio/bookdown}, 19 | } 20 | -------------------------------------------------------------------------------- /pkgdown/templates/in-header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | {{#includes}}{{{in_header}}}{{/includes}} 15 | -------------------------------------------------------------------------------- /tests/test-cran/test-addin.R: -------------------------------------------------------------------------------- 1 | assert('quote_poem() adds > to the beginning of every line, and two trailing spaces to every line', { 2 | 3 | (quote_poem(" ") %==% " ") 4 | 5 | (quote_poem("some text.") %==% '> some text.') 6 | 7 | (quote_poem("some text.\nsome more text.\neven more text.")) %==% 8 | "> some text. \n> some more text. \n> even more text." 9 | 10 | (quote_poem("some text. \nsome more text. \neven more text.")) %==% 11 | "> some text. \n> some more text. \n> even more text." 12 | 13 | (quote_poem("some text. \nsome more text. \n\neven more text.")) %==% 14 | "> some text. \n> some more text.\n>\n> even more text." 15 | 16 | }) 17 | -------------------------------------------------------------------------------- /man/config_Rprofile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{config_Rprofile} 4 | \alias{config_Rprofile} 5 | \title{Create or modify the \file{.Rprofile} file for a website project} 6 | \usage{ 7 | config_Rprofile() 8 | } 9 | \value{ 10 | As a side-effect, the file \file{.Rprofile} is created or modified. 11 | } 12 | \description{ 13 | If the file \file{.Rprofile} does not exist in the current directory, copy 14 | the file from the \file{resources} directory of \pkg{blogdown}. If the option 15 | \code{blogdown.hugo.version} is not found in this file, append 16 | \code{options(blogdown.hugo.version = "VERSION")} to it, where \code{VERSION} 17 | is obtained from \code{\link{hugo_version}()}. 18 | } 19 | -------------------------------------------------------------------------------- /docs/_render.R: -------------------------------------------------------------------------------- 1 | quiet = "--quiet" %in% commandArgs(FALSE) 2 | formats = commandArgs(TRUE) 3 | 4 | src = (function() { 5 | attr(body(sys.function()), 'srcfile') 6 | })()$filename 7 | if (is.null(src) || src == '') src = '.' 8 | owd = setwd(dirname(src)) 9 | 10 | # provide default formats if necessary 11 | if (length(formats) == 0) formats = c( 12 | 'bookdown::pdf_book', 'bookdown::epub_book', 'bookdown::gitbook' 13 | ) 14 | # render the book to all formats unless they are specified via command-line args 15 | for (fmt in formats) { 16 | cmd = sprintf("bookdown::render_book('index.Rmd', '%s', quiet = %s)", fmt, quiet) 17 | res = xfun::Rscript(c('-e', shQuote(cmd))) 18 | if (res != 0) stop('Failed to compile the book to ', fmt) 19 | } 20 | 21 | setwd(owd) 22 | -------------------------------------------------------------------------------- /man/blogdown.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/package.R 3 | \name{blogdown} 4 | \alias{blogdown} 5 | \alias{blogdown-package} 6 | \title{The \pkg{blogdown} package} 7 | \description{ 8 | The comprehensive documentation of this package is the book \bold{blogdown: 9 | Creating Websites with R Markdown} 10 | (\url{https://bookdown.org/yihui/blogdown/}). You are expected to read at 11 | least the first chapter. If you are really busy or do not care about an 12 | introduction to \pkg{blogdown} (e.g., you are very familiar with creating 13 | websites), set your working directory to an empty directory, and run 14 | \code{blogdown::\link{new_site}()} to get started right away. 15 | } 16 | \examples{ 17 | if (interactive()) blogdown::new_site() 18 | } 19 | -------------------------------------------------------------------------------- /pkgdown/assets/snippets/snippets.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function loadSnippet(snippet, mode) { 4 | mode = mode || "markdown"; 5 | $("#" + snippet).addClass("snippet"); 6 | var editor = ace.edit(snippet); 7 | editor.setHighlightActiveLine(false); 8 | editor.setShowPrintMargin(false); 9 | editor.setReadOnly(true); 10 | editor.setShowFoldWidgets(false); 11 | editor.renderer.setDisplayIndentGuides(false); 12 | editor.setTheme("ace/theme/textmate"); 13 | editor.$blockScrolling = Infinity; 14 | editor.session.setMode("ace/mode/" + mode); 15 | editor.session.getSelection().clearSelection(); 16 | 17 | $.get("snippets/" + snippet + ".md", function(data) { 18 | editor.setValue(data, -1); 19 | editor.setOptions({ 20 | maxLines: editor.session.getLength() 21 | }); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | To compile this book, open `index.Rmd` in the RStudio IDE, install the **blogdown** package from Github, and click the RStudio Addin "Preview Book". You should be able to preview the HTML version of the book. 2 | 3 | It is trickier to compile it to PDF. You need to install LaTeX. I recommend [TinyTeX](https://yihui.org/tinytex/): 4 | 5 | ```r 6 | tinytex::install_tinytex() 7 | ``` 8 | 9 | Then download and install three fonts from [Google Fonts](https://fonts.google.com/?query=source&selection.family=Alegreya|Alegreya+SC|Source+Code+Pro): Alegreya, Alegreya SC, and Source Code Pro. Run `make pdf` (if `make` is available) or `Rscript _render.R "bookdown::pdf_book"` in the `docs/` directory. Install LaTeX packages if missing. If you're using a Mac with OS X 10.8 or later, install [XQuartz](https://www.xquartz.org). 10 | -------------------------------------------------------------------------------- /inst/CITATION: -------------------------------------------------------------------------------- 1 | year = sub('.*(2[[:digit:]]{3})-.*', '\\1', meta$Date, perl = TRUE) 2 | vers = paste('R package version', meta$Version) 3 | if (length(year) == 0) year = format(Sys.Date(), '%Y') 4 | 5 | bibentry( 6 | 'Manual', 7 | title = paste(meta$Package, meta$Title, sep =": "), 8 | author = Filter(function(p) 'aut' %in% p$role, as.person(meta$Author)), 9 | year = year, 10 | note = vers, 11 | url = strsplit(meta$URL, ',')[[1]][1] 12 | ) 13 | 14 | bibentry( 15 | 'Book', 16 | title = 'blogdown: Creating Websites with {R} Markdown', 17 | author = as.person('Yihui Xie [aut], Alison Presmanes Hill [aut], Amber Thomas [aut]'), 18 | publisher = 'Chapman and Hall/CRC', 19 | address = 'Boca Raton, Florida', 20 | year = '2017', 21 | isbn = '978-0815363729', 22 | url = 'https://bookdown.org/yihui/blogdown/' 23 | ) 24 | -------------------------------------------------------------------------------- /man/build_dir.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{build_dir} 4 | \alias{build_dir} 5 | \title{Build all Rmd files under a directory} 6 | \usage{ 7 | build_dir(dir = ".", force = FALSE, ignore = "[.]Rproj$") 8 | } 9 | \arguments{ 10 | \item{dir}{A directory path.} 11 | 12 | \item{force}{Whether to force building all Rmd files. By default, an Rmd file 13 | is built only if it is newer than its output file(s).} 14 | 15 | \item{ignore}{A regular expression to match output filenames that should be 16 | ignored when testing if the modification time of the Rmd source file is 17 | newer than its output files.} 18 | } 19 | \description{ 20 | List all Rmd files recursively under a directory, and compile them using 21 | \code{\link[rmarkdown:render]{rmarkdown::render()}}. 22 | } 23 | -------------------------------------------------------------------------------- /docs/WIP.html: -------------------------------------------------------------------------------- 1 |
2 |

A note from the authors: Some of the information and instructions in this book are now out of date because of changes to Hugo and the blogdown package. 3 | If you have suggestions for improving this book, please file an issue in our GitHub repository. 4 | Thanks for your patience while we work to update the book, and please stay tuned for the revised version!

5 |

In the meantime, you can find an introduction to the changes and new features in the v1.0 release blog post and this "Up & running with blogdown in 2021" blog post.

6 |

— Yihui, Amber, & Alison

7 |
8 | -------------------------------------------------------------------------------- /tests/test-ci/test-themes.R: -------------------------------------------------------------------------------- 1 | library(testit) 2 | 3 | test_site = function(theme) { 4 | o = options(blogdown.hugo.args = c('--panicOnWarning', '--quiet')) 5 | on.exit(options(o), add = TRUE) 6 | dir.create(d1 <- tempfile()) 7 | on.exit(unlink(d1, recursive = TRUE), add = TRUE) 8 | d2 = new_site(d1, theme = theme, serve = FALSE) 9 | xfun::in_dir(d2, blogdown::build_site(build_rmd = 'newfile')) 10 | (xfun::normalize_path(d1) %==% xfun::normalize_path(d2) && 11 | xfun::in_dir(d2, blogdown::hugo_build(args = '--panicOnWarning')) == 0) 12 | } 13 | 14 | assert('new_site() and build_site() work with selected themes', { 15 | themes = c( 16 | 'hugo-apero/hugo-apero', 17 | sprintf('yihui/hugo-%s', c('lithium', 'prose', 'xmag', 'xmin', 'ivy', 'paged')) 18 | ) 19 | status = !sapply(themes, test_site) 20 | if (any(status)) stop( 21 | 'Theme(s) failed: ', paste(themes[status], collapse = ' '), call. = FALSE 22 | ) 23 | }) 24 | -------------------------------------------------------------------------------- /inst/rstudio/addins.dcf: -------------------------------------------------------------------------------- 1 | Name: Serve Site 2 | Description: Run blogdown::serve_site() to live preview a website locally. 3 | Binding: serve_site 4 | Interactive: true 5 | 6 | Name: New Post 7 | Description: Create a new post with blogdown::new_post(). 8 | Binding: new_post_addin 9 | Interactive: true 10 | 11 | Name: Update Metadata 12 | Description: Update the title, author, date, categories, and tags of the current blog post. 13 | Binding: update_meta_addin 14 | Interactive: true 15 | 16 | Name: Insert Image 17 | Description: Insert an external image into a blog post. 18 | Binding: insert_image_addin 19 | Interactive: true 20 | 21 | Name: Touch File 22 | Description: Change the timestamp of the current file in the editor. 23 | Binding: touch_file_rstudio 24 | Interactive: false 25 | 26 | Name: Quote Poem 27 | Description: Add > to the beginning of selected paragraphs and two trailing spaces to selected lines. 28 | Binding: quote_poem_addin 29 | Interactive: false 30 | -------------------------------------------------------------------------------- /inst/resources/Rprofile: -------------------------------------------------------------------------------- 1 | # REMEMBER to restart R after you modify and save this file! 2 | 3 | # First, execute the global .Rprofile if it exists. You may configure blogdown 4 | # options there, too, so they apply to any blogdown projects. Feel free to 5 | # ignore this part if it sounds too complicated to you. 6 | if (file.exists("~/.Rprofile")) { 7 | base::sys.source("~/.Rprofile", envir = environment()) 8 | } 9 | 10 | # Now set options to customize the behavior of blogdown for this project. Below 11 | # are a few sample options; for more options, see 12 | # https://bookdown.org/yihui/blogdown/global-options.html 13 | options( 14 | # to automatically serve the site on RStudio startup, set this option to TRUE 15 | blogdown.serve_site.startup = FALSE, 16 | # to disable knitting Rmd files on save, set this option to FALSE 17 | blogdown.knit.on_save = TRUE, 18 | # build .Rmd to .md; to build to .html (via Pandoc), set this option to 'html' 19 | blogdown.method = 'markdown' 20 | ) 21 | -------------------------------------------------------------------------------- /man/clean_duplicates.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/check.R 3 | \name{clean_duplicates} 4 | \alias{clean_duplicates} 5 | \title{Clean duplicated output files} 6 | \usage{ 7 | clean_duplicates(preview = TRUE) 8 | } 9 | \arguments{ 10 | \item{preview}{Whether to preview the file list, or just delete the files. If 11 | you are sure the files can be safely deleted, use \code{preview = FALSE}.} 12 | } 13 | \value{ 14 | For \code{preview = TRUE}, a logical vector indicating if each file 15 | was successfully deleted; for \code{preview = FALSE}, the file list is 16 | printed. 17 | } 18 | \description{ 19 | For an output file \file{FOO.html}, \file{FOO.md} should be deleted if 20 | \file{FOO.Rmd} exists, and \file{FOO.html} should be deleted when 21 | \file{FOO.Rmarkdown} exists (because \file{FOO.Rmarkdown} should generate 22 | \file{FOO.markdown} instead) or neither \file{FOO.Rmarkdown} nor 23 | \file{FOO.Rmd} exists (because a plain Markdown file should not be knitted to 24 | HTML). 25 | } 26 | -------------------------------------------------------------------------------- /docs/_output.yml: -------------------------------------------------------------------------------- 1 | bookdown::gitbook: 2 | includes: 3 | before_body: WIP.html 4 | css: css/style.css 5 | split_by: section 6 | config: 7 | toc: 8 | collapse: none 9 | before: | 10 |
  • Creating Websites with R Markdown
  • 11 | after: | 12 |
  • Published with bookdown
  • 13 | download: [pdf, epub] 14 | edit: https://github.com/rstudio/blogdown/edit/main/docs/%s 15 | sharing: 16 | github: yes 17 | facebook: no 18 | bookdown::pdf_book: 19 | includes: 20 | in_header: latex/preamble.tex 21 | before_body: latex/before_body.tex 22 | after_body: latex/after_body.tex 23 | keep_tex: yes 24 | dev: "cairo_pdf" 25 | latex_engine: xelatex 26 | citation_package: natbib 27 | template: null 28 | pandoc_args: --top-level-division=chapter 29 | toc_depth: 3 30 | toc_unnumbered: no 31 | toc_appendix: yes 32 | quote_footer: ["\\VA{", "}{}"] 33 | bookdown::epub_book: 34 | stylesheet: css/style.css 35 | -------------------------------------------------------------------------------- /inst/resources/2020-12-01-r-rmarkdown.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Hello R Markdown" 3 | author: "Frida Gomam" 4 | date: 2020-12-01T21:13:14-05:00 5 | categories: ["R"] 6 | tags: ["R Markdown", "plot", "regression"] 7 | --- 8 | 9 | ```{r setup, include=FALSE} 10 | knitr::opts_chunk$set(collapse = TRUE) 11 | ``` 12 | 13 | # R Markdown 14 | 15 | This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see . 16 | 17 | You can embed an R code chunk like this: 18 | 19 | ```{r cars} 20 | summary(cars) 21 | fit <- lm(dist ~ speed, data = cars) 22 | fit 23 | ``` 24 | 25 | # Including Plots 26 | 27 | You can also embed plots. See Figure \@ref(fig:pie) for example: 28 | 29 | ```{r pie, fig.cap='A fancy pie chart.', tidy=FALSE} 30 | par(mar = c(0, 1, 0, 1)) 31 | pie( 32 | c(280, 60, 20), 33 | c('Sky', 'Sunny side of pyramid', 'Shady side of pyramid'), 34 | col = c('#0292D8', '#F7EA39', '#C4B632'), 35 | init.angle = -50, border = NA 36 | ) 37 | ``` 38 | -------------------------------------------------------------------------------- /man/dep_path.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{dep_path} 4 | \alias{dep_path} 5 | \title{A helper function to return a dependency path name} 6 | \usage{ 7 | dep_path(default = knitr::opts_chunk$get("fig.path")) 8 | } 9 | \arguments{ 10 | \item{default}{Return this default value when this function is called outside 11 | of a \pkg{knitr} code chunk.} 12 | } 13 | \value{ 14 | A character string of the \code{default} value (outside \pkg{knitr}), 15 | or a path consisting of the \pkg{knitr} figure path appended by the current 16 | chunk label. 17 | } 18 | \description{ 19 | In most cases, \pkg{blogdown} can process images and HTML widgets 20 | automatically generated from code chunks (they will be moved to the 21 | \code{static/} folder by default), but it may fail to recognize dependency 22 | files generated to other paths. This function returns a path that you can use 23 | for your output files, so that \pkg{blogdown} knows that they should be be 24 | processed, too. It is designed to be used in a \pkg{knitr} code chunk. 25 | } 26 | -------------------------------------------------------------------------------- /man/hugo_installers.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/install.R 3 | \name{hugo_installers} 4 | \alias{hugo_installers} 5 | \title{Available Hugo installers of a version} 6 | \usage{ 7 | hugo_installers(version = "latest") 8 | } 9 | \arguments{ 10 | \item{version}{A version number. The default is to automatically detect the 11 | latest version. Versions before v0.17 are not supported.} 12 | } 13 | \value{ 14 | A data frame containing columns \code{os} (operating system), 15 | \code{arch} (architecture), and \code{extended} (extended version or not). 16 | If your R version is lower than 4.1.0, a character vector of the installer 17 | filenames will be returned instead. 18 | } 19 | \description{ 20 | Given a version number, return the information of available installers. If 21 | \code{\link{install_hugo}()} fails, you may run this function to check the 22 | available installers and obtain their \code{os}/\code{arch} info. 23 | } 24 | \examples{\dontshow{if (interactive()) withAutoprint(\{ # examplesIf} 25 | blogdown::hugo_installers() 26 | blogdown::hugo_installers("0.89.0") 27 | blogdown::hugo_installers("0.17") 28 | \dontshow{\}) # examplesIf} 29 | } 30 | -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | p.caption { 2 | color: #777; 3 | margin-top: 10px; 4 | } 5 | p code { 6 | white-space: inherit; 7 | } 8 | pre { 9 | word-break: normal; 10 | word-wrap: normal; 11 | } 12 | pre code { 13 | white-space: inherit; 14 | } 15 | p.flushright { 16 | text-align: right; 17 | } 18 | blockquote > p:last-child { 19 | text-align: right; 20 | } 21 | blockquote > p:first-child { 22 | text-align: inherit; 23 | } 24 | .header-section-number { 25 | padding-right: .2em; 26 | font-weight: 500; 27 | } 28 | .level1 .header-section-number { 29 | display: inline-block; 30 | border-bottom: 3px solid; 31 | } 32 | .level1 h1 { 33 | border-bottom: 1px solid; 34 | } 35 | h1, h2, h3, h4, h5, h6 { 36 | font-weight: normal; 37 | } 38 | h1.title { 39 | font-weight: 700; 40 | } 41 | .book .book-body .page-wrapper .page-inner section.normal strong { 42 | font-weight: 600; 43 | } 44 | 45 | .caution { 46 | border: 4px #096B7263; 47 | border-style: dashed solid; 48 | padding: 1em; 49 | margin: 1em 0; 50 | padding-left: 100px; 51 | background-size: 70px; 52 | background-repeat: no-repeat; 53 | background-position: 15px center; 54 | min-height: 120px; 55 | color: #096B72; 56 | background-color: #e6f3fc80; 57 | background-image: url("../images/caution.png"); 58 | } 59 | -------------------------------------------------------------------------------- /inst/rstudio/templates/project/skeleton.dcf: -------------------------------------------------------------------------------- 1 | Title: Website using blogdown 2 | Binding: blogdown_skeleton 3 | Subtitle: Create a new website using Hugo and blogdown 4 | Caption: Create a new website using Hugo and blogdown 5 | Icon: hugo-logo.png 6 | 7 | Parameter: theme 8 | Widget: TextInput 9 | Label: Hugo theme 10 | Default: yihui/hugo-lithium 11 | 12 | Parameter: format 13 | Widget: CheckboxInput 14 | Label: Convert the site config file to YAML 15 | Default: On 16 | 17 | Parameter: to_yaml 18 | Widget: CheckboxInput 19 | Label: Convert all post metadata to YAML 20 | Default: On 21 | 22 | Parameter: sample 23 | Widget: CheckboxInput 24 | Label: Add sample blog posts 25 | Default: On 26 | 27 | Parameter: theme_example 28 | Widget: CheckboxInput 29 | Label: Add the example site from the theme 30 | Default: On 31 | 32 | Parameter: empty_dirs 33 | Widget: CheckboxInput 34 | Label: Keep empty directories 35 | Default: Off 36 | Position: right 37 | 38 | Parameter: netlify 39 | Widget: CheckboxInput 40 | Label: Create netlify.toml 41 | Default: On 42 | Position: right 43 | 44 | Parameter: .Rprofile 45 | Widget: CheckboxInput 46 | Label: Create .Rprofile 47 | Default: On 48 | Position: right 49 | 50 | Parameter: install_hugo 51 | Widget: CheckboxInput 52 | Label: Install Hugo if not installed 53 | Default: On 54 | Position: right 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Feature request" 3 | about: Suggest an idea for this package 4 | title: '[FR]' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | 29 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to blogdown 2 | 3 | We welcome contributions to the **blogdown** package. 4 | 5 | You can contribute in many ways: 6 | 7 | * By opening issues to give feedback and share ideas. 8 | * By fixing typos in documentations 9 | * By submitting Pull Request (PR) to fix some opened issues 10 | * By submitting Pull Request (PR) to suggest some new features. (It is considered good practice to open issues before to discuss ideas) 11 | 12 | ## To submit a contribution using a Pull Request: 13 | 14 | 1. [Fork](https://github.com/rstudio/blogdown/fork) the repository and make your changes in a new branch specific to the PR. It is ok to edit a file in this repository using the `Edit` button on Github if the change is simple enough. 15 | 16 | 2. For significant changes (e.g not required for fixing typos), ensure that you have signed the [individual](https://www.rstudio.com/wp-content/uploads/2014/06/rstudioindividualcontributoragreement.pdf) or [corporate](https://www.rstudio.com/wp-content/uploads/2014/06/rstudiocorporatecontributoragreement.pdf) contributor agreement as appropriate. You can send the signed copy to . 17 | 18 | 3. Submit the [pull request](https://help.github.com/articles/using-pull-requests). It is ok to submit as draft if you are still working on it but would like some feedback from us. It always good to share in the open that you are working on it. 19 | 20 | We'll try to be as responsive as possible in reviewing and accepting pull requests. Appreciate your contributions very much! 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Bug report" 3 | about: Report an error or unexpected behavior you saw while using this package 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | 37 | -------------------------------------------------------------------------------- /man/find_yaml.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{find_yaml} 4 | \alias{find_yaml} 5 | \alias{find_tags} 6 | \alias{find_categories} 7 | \alias{count_yaml} 8 | \title{Find posts containing the specified metadata} 9 | \usage{ 10 | find_yaml(field = character(), value = character(), open = FALSE) 11 | 12 | find_tags(value = character(), open = FALSE) 13 | 14 | find_categories(value = character(), open = FALSE) 15 | 16 | count_yaml(fields = c("categories", "tags"), sort_by_count = TRUE) 17 | } 18 | \arguments{ 19 | \item{field, fields}{A character vector of YAML field names.} 20 | 21 | \item{value}{A vector of the field values to be matched.} 22 | 23 | \item{open}{Whether to open the matched files automatically.} 24 | 25 | \item{sort_by_count}{Whether to sort the frequency tables by counts.} 26 | } 27 | \value{ 28 | \code{find_yaml()} returns a character vector of filenames; 29 | \code{count_yaml()} returns a list of frequency tables. 30 | } 31 | \description{ 32 | Given a YAML field name, find the (R) Markdown files that contain this field 33 | and its value contains any of the specified values. Functions 34 | \code{find_tags()} and \code{find_categories()} are wrappers of 35 | \code{find_yaml()} with \code{field = 'tags'} and \code{field = 36 | 'categories'}, respectively; \code{count_fields()} returns the frequency 37 | tables of the specified YAML fields, such as the counts of tags and 38 | categories. 39 | } 40 | \examples{ 41 | library(blogdown) 42 | find_tags(c("time-series", "support vector machine")) 43 | find_categories("Statistics") 44 | 45 | count_yaml(sort_by_count = FALSE) 46 | } 47 | -------------------------------------------------------------------------------- /vignettes/articles/examples.yml: -------------------------------------------------------------------------------- 1 | - href: https://metadocencia.netlify.app/ 2 | source: https://github.com/MetaDocencia/SitioWeb 3 | title: MetaDocencia 4 | img: images/metadocencia.netlify.png 5 | showcase: yes 6 | - href: https://mariadermit.netlify.app/ 7 | source: https://github.com/demar01/mariadermit 8 | title: English site 9 | img: images/mariadermit.netlify.png 10 | showcase: yes 11 | - href: https://shinydevseries.com/ 12 | source: https://github.com/shinydevseries/shinydevseries_site 13 | title: Shiny Developer Series 14 | img: images/shinydevseries.png 15 | showcase: yes 16 | - href: https://r-podcast.org/ 17 | source: https://github.com/rbind/r-podcast 18 | title: The R-Podcast 19 | img: images/r-podcast.png 20 | showcase: yes 21 | - href: https://isabella-b.com/ 22 | source: https://github.com/isabellabenabaye/isabella-b.com 23 | title: Isabella Benabaye 24 | img: images/isabella-b.png 25 | showcase: yes 26 | - href: https://www.tidymodels.org/ 27 | source: https://github.com/tidymodels/tidymodels.org 28 | title: Tidymodels 29 | img: images/www.tidymodels.png 30 | showcase: yes 31 | - href: https://livefreeordichotomize.com/ 32 | source: https://github.com/LFOD/real-blog 33 | title: Live Free or Dichotomize - Live Free or Dichotomize 34 | img: images/livefreeordichotomize.png 35 | showcase: yes 36 | - href: https://hugo-apero-docs.netlify.app/ 37 | source: https://github.com/hugo-apero/hugo-apero-docs 38 | title: Hugo Apéro 39 | img: images/hugo-apero-docs.netlify.png 40 | showcase: yes 41 | - href: https://prose.yihui.org/ 42 | source: https://github.com/yihui/hugo-prose/tree/master/exampleSite 43 | title: Home | Hugo Prose 44 | img: images/prose.yihui.png 45 | showcase: yes 46 | -------------------------------------------------------------------------------- /tests/test-cran/test-clean.R: -------------------------------------------------------------------------------- 1 | library(testit) 2 | 3 | assert('remove_extra_empty_lines() replaces two or more empty lines with one', { 4 | 5 | x0 = 'a line with some new lines.\n\n and some text.' 6 | x1 = c('a line with some new lines.', '', '', '', ' and some text.') 7 | x2 = x1[-2] 8 | x3 = x1[-(2:3)] 9 | 10 | (remove_extra_empty_lines(x = x1) %==% x0) 11 | (remove_extra_empty_lines(x = x2) %==% x0) 12 | (remove_extra_empty_lines(x = x3) %==% x0) 13 | 14 | }) 15 | 16 | 17 | assert('process_bare_urls() replaces [url](url) with ', { 18 | 19 | x4 = '[url](url)' 20 | x5 = 'some text before [url](url) and after' 21 | 22 | (process_bare_urls(x = x4) %==% '') 23 | (process_bare_urls(x = x5) %==% 'some text before and after') 24 | 25 | }) 26 | 27 | 28 | assert('normalize_chars() converts curly quotes to straight quotes', { 29 | 30 | x6 = intToUtf8(8216:8217) 31 | x7 = intToUtf8(8220:8221) 32 | x8 = intToUtf8(8230) 33 | x9 = intToUtf8(160) 34 | 35 | (normalize_chars(x = x6) %==% "''") 36 | (normalize_chars(x = x7) %==% '""') 37 | (normalize_chars(x = x8) %==% '...') 38 | (normalize_chars(x = x9) %==% ' ') 39 | 40 | }) 41 | 42 | 43 | assert('remove_highlight_tags() cleans up code blocks syntax highlighted by Pandoc', { 44 | 45 | x10 = ' some code' 46 | x11 = ' some span' 47 | 48 | (remove_highlight_tags(x = x10) %==% ' some code') 49 | (remove_highlight_tags(x = x11) %==% ' some span') 50 | 51 | }) 52 | 53 | 54 | assert('fix_img_tags() converts to ', { 55 | 56 | x12 = '' 57 | x13 = 'text before and after' 58 | 59 | (fix_img_tags(x = x12) %==% '') 60 | (fix_img_tags(x = x13) %==% 'text before and after') 61 | 62 | }) 63 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(build_dir) 4 | export(build_site) 5 | export(bundle_site) 6 | export(check_config) 7 | export(check_content) 8 | export(check_gitignore) 9 | export(check_hugo) 10 | export(check_netlify) 11 | export(check_site) 12 | export(check_vercel) 13 | export(clean_duplicates) 14 | export(config_Rprofile) 15 | export(config_netlify) 16 | export(config_vercel) 17 | export(count_yaml) 18 | export(dep_path) 19 | export(edit_draft) 20 | export(filter_md5sum) 21 | export(filter_newfile) 22 | export(filter_timestamp) 23 | export(find_categories) 24 | export(find_hugo) 25 | export(find_tags) 26 | export(find_yaml) 27 | export(html_page) 28 | export(hugo_available) 29 | export(hugo_build) 30 | export(hugo_cmd) 31 | export(hugo_convert) 32 | export(hugo_installers) 33 | export(hugo_server) 34 | export(hugo_version) 35 | export(install_hugo) 36 | export(install_theme) 37 | export(new_content) 38 | export(new_post) 39 | export(new_site) 40 | export(read_toml) 41 | export(remove_hugo) 42 | export(serve_site) 43 | export(shortcode) 44 | export(shortcode_close) 45 | export(shortcode_html) 46 | export(shortcode_open) 47 | export(shortcodes) 48 | export(stop_server) 49 | export(toml2yaml) 50 | export(update_hugo) 51 | export(write_toml) 52 | export(yaml2toml) 53 | import(stats) 54 | import(utils) 55 | importFrom(xfun,del_empty_dir) 56 | importFrom(xfun,dir_create) 57 | importFrom(xfun,dir_exists) 58 | importFrom(xfun,existing_files) 59 | importFrom(xfun,exit_call) 60 | importFrom(xfun,file_exists) 61 | importFrom(xfun,file_ext) 62 | importFrom(xfun,in_dir) 63 | importFrom(xfun,is_macos) 64 | importFrom(xfun,is_windows) 65 | importFrom(xfun,msg_cat) 66 | importFrom(xfun,proc_kill) 67 | importFrom(xfun,read_utf8) 68 | importFrom(xfun,set_envvar) 69 | importFrom(xfun,write_utf8) 70 | -------------------------------------------------------------------------------- /R/clean.R: -------------------------------------------------------------------------------- 1 | # these functions are mainly for cleaning up Markdown files generated by Exitwp 2 | # with the XML file exported from WordPress 3 | 4 | # a wrapper function to read a file as UTF-8, process the text, and write back 5 | process_file = function(f, FUN, x = read_utf8(f)) { 6 | xfun::process_file(f, FUN, x) 7 | } 8 | 9 | # replace three or more \n with two, i.e. two or more empty lines with one 10 | remove_extra_empty_lines = function(...) xfun::process_file(..., fun = function(x) { 11 | x = paste(gsub('\\s+$', '', x), collapse = '\n') 12 | trim_ws(gsub('\n{3,}', '\n\n', x)) 13 | }) 14 | 15 | # replace [url](url) with 16 | process_bare_urls = function(...) xfun::process_file(..., fun = function(x) { 17 | gsub('\\[([^]]+)]\\(\\1/?\\)', '<\\1>', x) 18 | }) 19 | 20 | normalize_chars = function(...) xfun::process_file(..., fun = function(x) { 21 | # curly single and double quotes to straight quotes 22 | x = gsub(paste0('[', intToUtf8(8216:8217), ']'), "'", x) 23 | x = gsub(paste0('[', intToUtf8(8220:8221), ']'), '"', x) 24 | x = gsub(intToUtf8(8230), '...', x) # ellipses 25 | x = gsub(intToUtf8(160), ' ', x) # zero-width space 26 | x 27 | }) 28 | 29 | # clean up code blocks that have been syntax highlighted by Pandoc 30 | remove_highlight_tags = function(...) xfun::process_file(..., fun = function(x) { 31 | clean = function(x) { 32 | # remove the tags 33 | x = gsub('^(\\s+)(.*)', '\\1\\3', x) 34 | x = gsub('\\s*$', '', x) 35 | # remove 36 | x = gsub('])*>', '', x) 37 | x 38 | } 39 | # only process lines that are indented by at least 4 spaces 40 | i = grep('^( {4,}.*)', x) 41 | x[i] = clean(x[i]) 42 | x 43 | }) 44 | 45 | # to 46 | fix_img_tags = function(...) xfun::process_file(..., fun = function(x) { 47 | gsub('>', ' />', x) 48 | }) 49 | -------------------------------------------------------------------------------- /man/bundle_site.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/hugo.R 3 | \name{bundle_site} 4 | \alias{bundle_site} 5 | \title{Convert post files to leaf bundles} 6 | \usage{ 7 | bundle_site(dir = site_root(), output) 8 | } 9 | \arguments{ 10 | \item{dir}{The root directory of the website project (should contain a 11 | \file{content/} folder).} 12 | 13 | \item{output}{The output directory. If not provided, a suffix \file{-bundle} 14 | is added to the website root directory name. For example, the default 15 | output directory for the site under \file{~/Documents/test} is 16 | \file{~/Documents/test-bundle}. You can specify the output directory to be 17 | identical to the website root directory, so files will be moved within the 18 | same directory, but please remember that you will not be able to undo 19 | \code{bundle_site()}. You should modify the website in place \emph{only if 20 | you have a backup for this directory or it is under version control}.} 21 | } 22 | \description{ 23 | For a post with the path \file{content/path/to/my-post.md}, it will be moved 24 | to \file{content/path/to/my-post/index.md}, so it becomes the index file of a 25 | leaf bundle of Hugo. This also applies to files with extensions \file{.Rmd} 26 | and \file{.Rmarkdown}. 27 | } 28 | \note{ 29 | This function only moves (R) Markdown source files. If these files use 30 | resource files under the \file{static/} folder, these resources will not be 31 | moved into the \file{content/} folder. You need to manually move them, and 32 | adjust their paths in the (R) Markdown source files accordingly. 33 | } 34 | \examples{ 35 | \dontrun{ 36 | blogdown::bundle_site(".", "../new-site/") 37 | blogdown::bundle_site(".", ".") # move files within the current working directory 38 | } 39 | } 40 | \references{ 41 | Learn more about Hugo's leaf bundles at 42 | \url{https://gohugo.io/content-management/page-bundles/}. 43 | } 44 | -------------------------------------------------------------------------------- /docs/latex/preamble.tex: -------------------------------------------------------------------------------- 1 | \usepackage{booktabs} 2 | \usepackage{longtable} 3 | \usepackage[bf,singlelinecheck=off]{caption} 4 | 5 | \usepackage{Alegreya} 6 | \usepackage[scale=.7]{sourcecodepro} 7 | 8 | \usepackage{framed,color} 9 | \definecolor{shadecolor}{RGB}{248,248,248} 10 | 11 | \renewcommand{\textfraction}{0.05} 12 | \renewcommand{\topfraction}{0.8} 13 | \renewcommand{\bottomfraction}{0.8} 14 | \renewcommand{\floatpagefraction}{0.75} 15 | 16 | \renewenvironment{quote}{\begin{VF}}{\end{VF}} 17 | \let\oldhref\href 18 | \renewcommand{\href}[2]{#2\footnote{\url{#1}}} 19 | 20 | \ifxetex 21 | \usepackage{letltxmacro} 22 | \setlength{\XeTeXLinkMargin}{1pt} 23 | \LetLtxMacro\SavedIncludeGraphics\includegraphics 24 | \def\includegraphics#1#{% #1 catches optional stuff (star/opt. arg.) 25 | \IncludeGraphicsAux{#1}% 26 | }% 27 | \newcommand*{\IncludeGraphicsAux}[2]{% 28 | \XeTeXLinkBox{% 29 | \SavedIncludeGraphics#1{#2}% 30 | }% 31 | }% 32 | \fi 33 | 34 | \makeatletter 35 | \newenvironment{kframe}{% 36 | \medskip{} 37 | \setlength{\fboxsep}{.8em} 38 | \def\at@end@of@kframe{}% 39 | \ifinner\ifhmode% 40 | \def\at@end@of@kframe{\end{minipage}}% 41 | \begin{minipage}{\columnwidth}% 42 | \fi\fi% 43 | \def\FrameCommand##1{\hskip\@totalleftmargin \hskip-\fboxsep 44 | \colorbox{shadecolor}{##1}\hskip-\fboxsep 45 | % There is no \\@totalrightmargin, so: 46 | \hskip-\linewidth \hskip-\@totalleftmargin \hskip\columnwidth}% 47 | \MakeFramed {\advance\hsize-\width 48 | \@totalleftmargin\z@ \linewidth\hsize 49 | \@setminipage}}% 50 | {\par\unskip\endMakeFramed% 51 | \at@end@of@kframe} 52 | \makeatother 53 | 54 | \renewenvironment{Shaded}{\begin{kframe}}{\end{kframe}} 55 | 56 | \usepackage{makeidx} 57 | \makeindex 58 | 59 | \urlstyle{tt} 60 | 61 | \usepackage{amsthm} 62 | \makeatletter 63 | \def\thm@space@setup{% 64 | \thm@preskip=8pt plus 2pt minus 4pt 65 | \thm@postskip=\thm@preskip 66 | } 67 | \makeatother 68 | 69 | \frontmatter 70 | -------------------------------------------------------------------------------- /man/install_theme.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/hugo.R 3 | \name{install_theme} 4 | \alias{install_theme} 5 | \title{Install a Hugo theme from Github} 6 | \usage{ 7 | install_theme( 8 | theme, 9 | hostname = "github.com", 10 | theme_example = FALSE, 11 | update_config = TRUE, 12 | force = FALSE, 13 | update_hugo = TRUE 14 | ) 15 | } 16 | \arguments{ 17 | \item{theme}{A Hugo theme on Github (a character string of the form 18 | \code{user/repo}, and you can optionally specify a GIT branch or tag name 19 | after \code{@}, i.e. \code{theme} can be of the form 20 | \code{user/repo@branch}). You can also specify a full URL to the zip file 21 | or tarball of the theme. If \code{theme = NA}, no themes will be installed, 22 | and you have to manually install a theme.} 23 | 24 | \item{hostname}{Where to find the theme. Defaults to \code{github.com}; 25 | specify if you wish to use an instance of GitHub Enterprise. You can also 26 | specify the full URL of the zip file or tarball in \code{theme}, in which 27 | case this argument is ignored.} 28 | 29 | \item{theme_example}{Whether to copy the example in the \file{exampleSite} 30 | directory if it exists in the theme. Not all themes provide example sites.} 31 | 32 | \item{update_config}{Whether to update the \code{theme} option in the site 33 | configurations.} 34 | 35 | \item{force}{Whether to override the existing theme of the same name. If you 36 | have made changes to this existing theme, your changes will be lost when 37 | \code{force = TRUE}! Please consider backing up the theme by renaming it 38 | before you try \code{force = TRUE}.} 39 | 40 | \item{update_hugo}{Whether to automatically update Hugo if the theme requires 41 | a higher version of Hugo than the existing version in your system.} 42 | } 43 | \description{ 44 | Download the specified theme from Github and install to the \file{themes} 45 | directory. Available themes are listed at \url{https://themes.gohugo.io}. 46 | } 47 | -------------------------------------------------------------------------------- /R/package.R: -------------------------------------------------------------------------------- 1 | #' The \pkg{blogdown} package 2 | #' 3 | #' The comprehensive documentation of this package is the book \bold{blogdown: 4 | #' Creating Websites with R Markdown} 5 | #' (\url{https://bookdown.org/yihui/blogdown/}). You are expected to read at 6 | #' least the first chapter. If you are really busy or do not care about an 7 | #' introduction to \pkg{blogdown} (e.g., you are very familiar with creating 8 | #' websites), set your working directory to an empty directory, and run 9 | #' \code{blogdown::\link{new_site}()} to get started right away. 10 | #' @name blogdown 11 | #' @aliases blogdown-package 12 | #' @import utils 13 | #' @import stats 14 | #' @importFrom xfun in_dir read_utf8 write_utf8 is_windows is_macos 15 | #' file_exists dir_exists file_ext msg_cat dir_create del_empty_dir proc_kill 16 | #' set_envvar exit_call existing_files 17 | #' @examples if (interactive()) blogdown::new_site() 18 | NULL 19 | 20 | with_ext = function(...) xfun::with_ext(...) 21 | fetch_yaml = function(f) bookdown:::fetch_yaml(read_utf8(f)) 22 | 23 | `%n%` = knitr:::`%n%` 24 | 25 | blogdown_skeleton = function(path, ...) { 26 | opts = options(blogdown.open_sample = FALSE); on.exit(options(opts), add = TRUE) 27 | new_site(dir = path, ..., serve = FALSE) 28 | } 29 | 30 | .onLoad = function(libname, pkgname) { 31 | # stop all servers when the package is unloaded or R session is ended 32 | reg.finalizer(asNamespace(pkgname), function(e) { 33 | opts$set(quitting = TRUE); on.exit(opts$set(quitting = NULL), add = TRUE) 34 | stop_server() 35 | }, onexit = TRUE) 36 | 37 | # initialize some important global options, so RStudio could autocomplete 38 | # options(); I can't set them to NULL directly because options(foo = NULL) 39 | # would *remove* the option 'foo', so RStudio won't be able to recognize 40 | # option names (I have to set them to I(NA) instead); I hate this ugly hack 41 | if (interactive()) for (i in names(.options)) { 42 | if (is.null(getOption(i))) options(.options[i]) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /man/config_netlify.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{config_netlify} 4 | \alias{config_netlify} 5 | \title{Create the configuration (file) for Netlify} 6 | \usage{ 7 | config_netlify(output = "netlify.toml", new_config = list()) 8 | } 9 | \arguments{ 10 | \item{output}{Path to the output file, or \code{NULL}. If the file exists and 11 | the R session is interactive, you will be prompted to decide whether to 12 | overwrite the file.} 13 | 14 | \item{new_config}{If any default configuration does not apply to your site, 15 | you may provide a list of configurations to override the default. For 16 | example, if you want to use Hugo v0.25.1, you may use \code{new_config = 17 | list(build = list(environment = list(HUGO_VERSION = '0.25.1')))}.} 18 | } 19 | \value{ 20 | If \code{output = NULL}, a character vector of TOML data representing 21 | the configurations (which you can preview and decide whether to write it to 22 | a file), otherwise the TOML data is written to a file. 23 | } 24 | \description{ 25 | This function provides some default configurations for a Huge website to be 26 | built via Hugo and deployed on Netlify. It sets the build command for the 27 | production and preview contexts, respectively (for preview contexts such as 28 | \samp{deploy-preview}, the command will build future posts). It also sets the 29 | publish directory according to your setting in Hugo's config file (if it 30 | exists, otherwise it will be the default \file{public} directory). The Hugo 31 | version is set to the current version of Hugo found on your computer. 32 | } 33 | \examples{ 34 | blogdown::config_netlify(output = NULL) # default data 35 | 36 | # change the publish dir to 'docs/' 37 | blogdown::config_netlify(NULL, list(build = list(publish = "docs"))) 38 | } 39 | \references{ 40 | See Netlify's documentation on the configuration file 41 | \file{netlify.toml} for the possible settings: 42 | \url{https://docs.netlify.com/configure-builds/file-based-configuration/} 43 | } 44 | -------------------------------------------------------------------------------- /R/addin.R: -------------------------------------------------------------------------------- 1 | source_addin = function(file) in_root(sys.source( 2 | pkg_file('scripts', file), envir = new.env(parent = globalenv()), 3 | keep.source = FALSE 4 | )) 5 | 6 | new_post_addin = function() { 7 | if (generator() == 'hugo') find_hugo() 8 | source_addin('new_post.R') 9 | } 10 | 11 | update_meta_addin = function() source_addin('update_meta.R') 12 | 13 | insert_image_addin = function() { 14 | # when the addin is not used inside a site project 15 | if (xfun::try_error(site_root())) { 16 | old = opts$get() 17 | opts$set(site_root = I('.')) 18 | on.exit(opts$restore(old), add = TRUE) 19 | } 20 | source_addin('insert_image.R') 21 | } 22 | 23 | # use touch to update the timestamp of a file if available (not on Windows); 24 | # otherwise modify a file, undo it, and save it 25 | touch_file_rstudio = function() { 26 | ctx = rstudioapi::getSourceEditorContext() 27 | if (!file.exists(ctx$path)) stop('The current document has not been saved yet.') 28 | p = normalizePath(ctx$path); mtime = function() file.info(p)[, 'mtime'] 29 | m = mtime() 30 | on.exit(if (!identical(m, m2 <- mtime())) message( 31 | 'The modification time of "', p, '" has been updated from ', m, ' to ', m2 32 | ), add = TRUE) 33 | touch_file(p) 34 | } 35 | 36 | touch_file = function(path, time = Sys.time()) Sys.setFileTime(path, time) 37 | 38 | # add > to the beginning of every line, and two trailing spaces to every line 39 | quote_poem = function(x) { 40 | x = paste(x, collapse = '\n') 41 | if (grepl('^\\s*$', x)) return(x) 42 | x = gsub(' *\n', ' \n', x) 43 | x = unlist(strsplit(x, '( *\n){2,}')) 44 | x = gsub('(^| \n)', '\\1> ', x) 45 | x = paste(x, collapse = '\n>\n') 46 | gsub(' \n> $', '\n', x) 47 | } 48 | 49 | quote_poem_addin = function() { 50 | ctx = rstudioapi::getSourceEditorContext() 51 | sel = ctx$selection[[1]] 52 | if (sel$text == '') { 53 | message('Please select some text in the editor first.') 54 | return() 55 | } 56 | rstudioapi::modifyRange(sel$range, quote_poem(sel$text)) 57 | } 58 | -------------------------------------------------------------------------------- /man/filter_newfile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{filter_newfile} 4 | \alias{filter_newfile} 5 | \alias{filter_timestamp} 6 | \alias{filter_md5sum} 7 | \title{Look for files that have been possibly modified or out-of-date} 8 | \usage{ 9 | filter_newfile(files) 10 | 11 | filter_timestamp(files) 12 | 13 | filter_md5sum(files, db = "blogdown/md5sum.txt") 14 | } 15 | \arguments{ 16 | \item{files}{A vector of file paths.} 17 | 18 | \item{db}{Path to the database file.} 19 | } 20 | \value{ 21 | The filtered file paths. 22 | } 23 | \description{ 24 | Filter files by checking if their modification times or MD5 checksums have 25 | changed. 26 | } 27 | \details{ 28 | The function \code{filter_newfile()} returns paths of source files that do 29 | not have corresponding output files, e.g., an \file{.Rmd} file that doesn't 30 | have the \file{.html} output file. 31 | 32 | The function \code{filter_timestamp()} compares the modification time of an 33 | Rmd file with that of its output file, and returns the path of a file if it 34 | is newer than its output file by \code{N} seconds (or if the output file does 35 | not exist), where \code{N} is obtained from the R global option 36 | \code{blogdown.time_diff}. By default, \code{N = 0}. You may change it via 37 | \code{options()}, e.g., \code{options(blogdown.time_diff = 5)} means an Rmd 38 | file will be returned when its modification time at least 5 seconds newer 39 | than its output file's modification time. 40 | 41 | The function \code{filter_md5sum()} reads the MD5 checksums of files from a 42 | database (a tab-separated text file), and returns the files of which the 43 | checksums have changed. If the database does not exist, write the checksums 44 | of files to it, otherwise update the checksums after the changed files have 45 | been identified. When a file is modified, its MD5 checksum is very likely to 46 | change. 47 | 48 | These functions can be used to determine which Rmd files to be rebuilt in a 49 | \pkg{blogdown} website. See \code{\link{build_site}()} for more information. 50 | } 51 | -------------------------------------------------------------------------------- /man/html_page.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/format.R 3 | \name{html_page} 4 | \alias{html_page} 5 | \title{An R Markdown output format for \pkg{blogdown} web pages} 6 | \usage{ 7 | html_page( 8 | ..., 9 | number_sections = FALSE, 10 | self_contained = FALSE, 11 | highlight = NULL, 12 | template = NULL, 13 | pandoc_args = c("-M", "link-citations=true", "--preserve-tabs"), 14 | keep_md = FALSE, 15 | pre_knit = NULL, 16 | post_processor = NULL 17 | ) 18 | } 19 | \arguments{ 20 | \item{..., number_sections, self_contained, highlight, template, pandoc_args}{Arguments passed to \code{bookdown::html_document2()} (note the option \code{theme} 21 | is not supported and set to \code{NULL} internally, and when \code{template = NULL}, 22 | a default template in \pkg{blogdown} will be used).} 23 | 24 | \item{keep_md, pre_knit, post_processor}{Passed to \link[rmarkdown:output_format]{rmarkdown::output_format}.} 25 | } 26 | \description{ 27 | This function is a simple wrapper of \code{\link[bookdown:html_document2]{bookdown::html_document2()}} with 28 | different default arguments, and more importantly, a special HTML template 29 | designed only for \pkg{blogdown} to render R Markdown to HTML pages that can 30 | be processed by Hugo. 31 | } 32 | \details{ 33 | The HTML output is not a complete HTML document, and only meaningful to 34 | \pkg{blogdown} (it will be post-processed to render valid HTML pages). The 35 | only purpose of this output format is for users to change options in YAML. 36 | 37 | The fact that it is based on \pkg{bookdown} means most \pkg{bookdown} 38 | features are supported, such as numbering and cross-referencing 39 | figures/tables. 40 | } 41 | \note{ 42 | Do not use a custom template unless you understand how the default 43 | template actually works (see the \pkg{blogdown} book). 44 | 45 | The argument \code{highlight} does not support the value \code{"textmate"}, and the 46 | argument \code{template} does not support the value \code{"default"}. 47 | } 48 | \references{ 49 | See Chapter 2 of the \pkg{bookdown} book for the Markdown syntax: 50 | \url{https://bookdown.org/yihui/bookdown}. See the \pkg{blogdown} book for full 51 | details: \url{https://bookdown.org/yihui/blogdown}. 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: main 4 | paths-ignore: 5 | - 'docs/**' 6 | - '.github/ISSUE_TEMPLATE' 7 | - '.github/workflows/bookdown.yaml' 8 | - '_pkgdown.yml' 9 | - 'pkgdown/**' 10 | - '.github/workflows/pkgdown.yaml' 11 | pull_request: 12 | branches: main 13 | paths-ignore: 14 | - 'docs/**' 15 | - '.github/ISSUE_TEMPLATE' 16 | - '.github/workflows/bookdown.yaml' 17 | - '_pkgdown.yml' 18 | - 'pkgdown/**' 19 | - '.github/workflows/pkgdown.yaml' 20 | schedule: 21 | - cron: '0 10 * * 1' 22 | 23 | name: R-CMD-check 24 | 25 | jobs: 26 | R-CMD-check: 27 | runs-on: ${{ matrix.config.os }} 28 | 29 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 30 | 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | config: 35 | - {os: windows-latest, r: 'release'} 36 | - {os: macos-latest, r: 'release'} 37 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 38 | - {os: ubuntu-latest, r: 'release'} 39 | - {os: ubuntu-latest, r: 'oldrel'} 40 | 41 | env: 42 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 43 | R_KEEP_PKG_SOURCE: yes 44 | 45 | steps: 46 | - uses: actions/checkout@HEAD 47 | 48 | - uses: r-lib/actions/setup-pandoc@HEAD 49 | 50 | - uses: r-lib/actions/setup-r@HEAD 51 | with: 52 | r-version: ${{ matrix.config.r }} 53 | http-user-agent: ${{ matrix.config.http-user-agent }} 54 | use-public-rspm: true 55 | 56 | - uses: r-lib/actions/setup-r-dependencies@HEAD 57 | with: 58 | extra-packages: any::rcmdcheck 59 | needs: check 60 | 61 | - name: Install Hugo using blogdown 62 | run: | 63 | pak::local_install() 64 | blogdown::install_hugo() 65 | blogdown::hugo_version() 66 | shell: Rscript {0} 67 | 68 | - uses: r-lib/actions/check-r-package@HEAD 69 | 70 | - name: Test coverage 71 | if: success() && runner.os == 'Linux' && matrix.config.r == 'release' 72 | run: | 73 | pak::pkg_install('covr') 74 | covr::codecov() 75 | shell: Rscript {0} 76 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main, master] 4 | pull_request: 5 | branches: [main, master] 6 | release: 7 | types: [published] 8 | workflow_dispatch: 9 | 10 | name: pkgdown 11 | 12 | jobs: 13 | pkgdown: 14 | if: ${{ github.event_name == 'push' || startsWith(github.head_ref, 'pkgdown/') }} 15 | runs-on: ubuntu-latest 16 | # Only restrict concurrency for non-PR jobs 17 | concurrency: 18 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 19 | env: 20 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 21 | steps: 22 | - uses: actions/checkout@HEAD 23 | 24 | - uses: r-lib/actions/setup-pandoc@HEAD 25 | 26 | - uses: r-lib/actions/setup-r@HEAD 27 | with: 28 | use-public-rspm: true 29 | 30 | - uses: r-lib/actions/setup-r-dependencies@HEAD 31 | with: 32 | extra-packages: any::pkgdown, local::. 33 | needs: website 34 | 35 | - name: Install optipng 36 | # required to optimize images 37 | run: | 38 | sudo apt-get update -y 39 | sudo apt-get install -y optipng 40 | 41 | - name: Cache some pkgdown assets 42 | uses: actions/cache@HEAD 43 | with: 44 | path: | 45 | vignettes/articles/images/*.png 46 | key: 1-${{ hashFiles('vignettes/articles/examples.yml') }} 47 | 48 | - name: Build pkgdown site 49 | run: pkgdown::build_site(new_process = FALSE, install = FALSE) 50 | shell: Rscript {0} 51 | 52 | - name: Deploy to Netlify 53 | id: netlify-deploy 54 | uses: nwtgck/actions-netlify@v2 55 | with: 56 | publish-dir: 'reference' 57 | production-branch: main 58 | github-token: ${{ secrets.GITHUB_TOKEN }} 59 | deploy-message: 60 | 'Deploy from GHA: ${{ github.event.head_commit.message }} (${{ github.sha }})' 61 | enable-pull-request-comment: false 62 | enable-commit-comment: false 63 | enable-commit-status: true 64 | alias: deploy-preview-${{ github.event.number }} 65 | env: 66 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 67 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} 68 | -------------------------------------------------------------------------------- /man/find_hugo.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/install.R 3 | \name{find_hugo} 4 | \alias{find_hugo} 5 | \alias{remove_hugo} 6 | \title{Find or remove the Hugo executable} 7 | \usage{ 8 | find_hugo(version = getOption("blogdown.hugo.version"), quiet = FALSE) 9 | 10 | remove_hugo(version = getOption("blogdown.hugo.version"), force = FALSE) 11 | } 12 | \arguments{ 13 | \item{version}{The expected version number, e.g., \code{'0.25.1'}. If 14 | \code{NULL}, it will try to find/remove the maximum possible version. If 15 | \code{'all'}, find/remove all possible versions. In an interactive R 16 | session when \code{version} is not provided, \code{remove_hugo()} will list 17 | all installed versions of Hugo, and you can select which versions to 18 | remove.} 19 | 20 | \item{quiet}{Whether to signal a message when two versions of Hugo are found: 21 | one is found on the system \var{PATH} variable, and one is installed by 22 | \code{\link{install_hugo}()}.} 23 | 24 | \item{force}{By default, \code{remove_hugo()} only removes Hugo installed via 25 | \code{\link{install_hugo}()}. For \code{force = TRUE}, it will try to 26 | remove any Hugo executables found via \code{find_hugo()}.} 27 | } 28 | \value{ 29 | For \code{find_hugo()}, it returns the path to the Hugo executable if 30 | found, otherwise it will signal an error, with a hint on how to install 31 | (the required version of) Hugo. If Hugo is found via the environment 32 | variable \var{PATH}, only the base name of the path is returned (you may 33 | use \code{\link{Sys.which}('hugo')} to obtain the full path). 34 | 35 | If \code{version = 'all'}, return the paths of all versions of Hugo 36 | installed. 37 | } 38 | \description{ 39 | Search for Hugo in a series of possible installation directories (see 40 | \code{\link{install_hugo}()} for these directories) with \code{find_hugo()}, 41 | or remove the Hugo executable(s) found with \code{remove_hugo()}. 42 | } 43 | \details{ 44 | If your website depends on a specific version of Hugo, we strongly recommend 45 | that you set \code{options(blogdown.hugo.version = )} to the version number 46 | you desire in the file \code{.Rprofile} in the root directory of the website 47 | project, so that \pkg{blogdown} can try to find the right version of Hugo 48 | before it builds or serves the website. You can use the function 49 | \code{\link{config_Rprofile}()} to do this automatically. 50 | } 51 | -------------------------------------------------------------------------------- /man/read_toml.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{read_toml} 4 | \alias{read_toml} 5 | \alias{write_toml} 6 | \alias{toml2yaml} 7 | \alias{yaml2toml} 8 | \title{Read and write TOML data (Tom's Obvious Markup Language)} 9 | \usage{ 10 | read_toml(file, x = read_utf8(file), strict = TRUE) 11 | 12 | write_toml(x, output = NULL) 13 | 14 | toml2yaml(file, output = NULL) 15 | 16 | yaml2toml(file, output = NULL) 17 | } 18 | \arguments{ 19 | \item{file}{Path to an input (TOML or YAML) file.} 20 | 21 | \item{x}{For \code{read_toml()}, the TOML data as a character vector (it is read 22 | from \code{file} by default; if provided, \code{file} will be ignored). For 23 | \code{write_toml()}, an R object to be converted to TOML.} 24 | 25 | \item{strict}{Whether to try \pkg{RcppTOML} and Hugo only (i.e., not to use 26 | the naive parser). If \code{FALSE}, only the naive parser is used (this is not 27 | recommended, unless you are sure your TOML data is really simple).} 28 | 29 | \item{output}{Path to an output file. If \code{NULL}, the TOML data is 30 | returned, otherwise the data is written to the specified file.} 31 | } 32 | \value{ 33 | For \code{read_toml()}, an R object. For \code{write_toml()}, \code{toml2yaml()}, 34 | and \code{yaml2toml()}, a character vector (marked by \code{\link[xfun:raw_string]{xfun::raw_string()}}) of 35 | the TOML/YAML data if \code{output = NULL}, otherwise the TOML/YAML data is 36 | written to the output file. 37 | } 38 | \description{ 39 | The function \code{read_toml()} reads TOML data from a file or a character vector, 40 | and the function \code{write_toml()} converts an R object to TOML. 41 | } 42 | \details{ 43 | For \code{read_toml()}, it first tries to use the R package \pkg{RcppTOML} to read 44 | the TOML data. If \pkg{RcppTOML} is not available, it uses Hugo to convert 45 | the TOML data to YAML, and reads the YAML data via the R package \pkg{yaml}. 46 | If Hugo is not available, it falls back to a naive parser, which is only able 47 | to parse top-level fields in the TOML data, and it only supports character, 48 | logical, and numeric (including integer) scalars. 49 | 50 | For \code{write_toml()}, it converts an R object to YAML via the R package 51 | \pkg{yaml}, and uses Hugo to convert the YAML data to TOML. 52 | } 53 | \examples{ 54 | \dontrun{ 55 | v = blogdown::read_toml(x = c("a = 1", "b = true", "c = \"Hello\"", "d = [1, 2]")) 56 | v 57 | blogdown::write_toml(v) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /man/serve_site.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/serve.R 3 | \name{serve_site} 4 | \alias{serve_site} 5 | \alias{stop_server} 6 | \title{Live preview a site} 7 | \usage{ 8 | serve_site(..., .site_dir = NULL) 9 | 10 | stop_server() 11 | } 12 | \arguments{ 13 | \item{...}{Arguments passed to \code{servr::\link[servr]{server_config}()} 14 | (only arguments \code{host}, \code{port}, \code{browser}, \code{daemon}, 15 | and \code{interval} are supported).} 16 | 17 | \item{.site_dir}{Directory to search for site configuration file. It defaults 18 | to \code{getwd()}, and can also be specified via the global option 19 | \code{blogdown.site_root}.} 20 | } 21 | \description{ 22 | The function \code{serve_site()} executes the server command of a static site 23 | generator (e.g., \command{hugo server} or \command{jekyll server}) to start a 24 | local web server, which watches for changes in the site, rebuilds the site if 25 | necessary, and refreshes the web page automatically; \code{stop_server()} 26 | stops the web server. 27 | } 28 | \details{ 29 | By default, the server also watches for changes in R Markdown files, and 30 | recompile them automatically if they are modified. This means they will be 31 | automatically recompiled once you save them. If you do not like this 32 | behavior, you may set \code{options(blogdown.knit.on_save = FALSE)} (ideally 33 | in your \file{.Rprofile}). When this feature is disabled, you will have to 34 | manually compile Rmd documents, e.g., by clicking the Knit button in RStudio. 35 | 36 | The site generator is defined by the global R option 37 | \code{blogdown.generator}, with the default being \code{'hugo'}. You may use 38 | other site generators including \code{jekyll} and \code{hexo}, e.g., 39 | \code{options(blogdown.generator = 'jekyll')}. You can define command-line 40 | arguments to be passed to the server of the site generator via the global R 41 | option \code{blogdown.X.server}, where \code{X} is \code{hugo}, 42 | \code{jekyll}, or \code{hexo}. The default for Hugo is 43 | \code{options(blogdown.hugo.server = c('-D', '-F', '--navigateToChanged'))} 44 | (see the documentation of Hugo server at 45 | \url{https://gohugo.io/commands/hugo_server/} for the meaning of these 46 | arguments). 47 | } 48 | \note{ 49 | For the Hugo server, the argument \command{--navigateToChanged} is used 50 | by default, which means when you edit and save a source file, Hugo will 51 | automatically navigate the web browser to the page corresponding to this 52 | source file (if the page exists). 53 | } 54 | -------------------------------------------------------------------------------- /pkgdown/assets/ace-1.2.3/theme-textmate.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/theme/textmate",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";t.isDark=!1,t.cssClass="ace-tm",t.cssText='.ace-tm .ace_gutter {background: #f0f0f0;color: #333;}.ace-tm .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-tm .ace_fold {background-color: #6B72E6;}.ace-tm {background-color: #FFFFFF;color: black;}.ace-tm .ace_cursor {color: black;}.ace-tm .ace_invisible {color: rgb(191, 191, 191);}.ace-tm .ace_storage,.ace-tm .ace_keyword {color: blue;}.ace-tm .ace_constant {color: rgb(197, 6, 11);}.ace-tm .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-tm .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-tm .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-tm .ace_invalid {background-color: rgba(255, 0, 0, 0.1);color: red;}.ace-tm .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-tm .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-tm .ace_support.ace_type,.ace-tm .ace_support.ace_class {color: rgb(109, 121, 222);}.ace-tm .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-tm .ace_string {color: rgb(3, 106, 7);}.ace-tm .ace_comment {color: rgb(76, 136, 107);}.ace-tm .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-tm .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-tm .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-tm .ace_variable {color: rgb(49, 132, 149);}.ace-tm .ace_xml-pe {color: rgb(104, 104, 91);}.ace-tm .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-tm .ace_heading {color: rgb(12, 7, 255);}.ace-tm .ace_list {color:rgb(185, 6, 144);}.ace-tm .ace_meta.ace_tag {color:rgb(0, 22, 142);}.ace-tm .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-tm .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-tm.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;}.ace-tm .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-tm .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-tm .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-tm .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-tm .ace_gutter-active-line {background-color : #dcdcdc;}.ace-tm .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-tm .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}) -------------------------------------------------------------------------------- /.github/workflows/bookdown.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - main 6 | paths: 7 | - 'docs/**' 8 | - '.github/workflows/bookdown.yaml' 9 | pull_request: 10 | branches: [main, master] 11 | paths: 12 | - 'docs/**' 13 | - '.github/workflows/bookdown.yaml' 14 | workflow_dispatch: 15 | inputs: 16 | publish: 17 | description: 'publish the book to github pages for connect cloud deployment' 18 | required: false 19 | default: false 20 | type: boolean 21 | 22 | name: Build and deploy book 23 | 24 | jobs: 25 | build: 26 | runs-on: macOS-latest 27 | env: 28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 29 | R_KNITR_OPTIONS: "knitr.chunk.tidy=TRUE" 30 | steps: 31 | - name: Checkout repo 32 | uses: actions/checkout@HEAD 33 | 34 | - name: Setup R 35 | uses: r-lib/actions/setup-r@HEAD 36 | 37 | - name: Install Pandoc 38 | uses: r-lib/actions/setup-pandoc@HEAD 39 | with: 40 | pandoc-version: '2.11.4' 41 | 42 | - name: Install TinyTeX 43 | uses: r-lib/actions/setup-tinytex@HEAD 44 | env: 45 | # install full prebuilt version 46 | TINYTEX_INSTALLER: TinyTeX 47 | 48 | - name: Install OS dependencies 49 | run: | 50 | brew install --cask xquartz 51 | brew install --cask calibre 52 | 53 | - uses: r-lib/actions/setup-r-dependencies@HEAD 54 | with: 55 | extra-packages: local::. 56 | needs: book 57 | 58 | - name: Cache bookdown results 59 | uses: actions/cache@HEAD 60 | with: 61 | path: docs/_bookdown_files 62 | key: bookdown-1-${{ hashFiles('docs/*Rmd') }} 63 | restore-keys: bookdown-1- 64 | 65 | - name: Build all book 66 | run: make -C docs all 67 | 68 | - name: Deploy Gitbook to gh-pages 69 | if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' 70 | uses: JamesIves/github-pages-deploy-action@v4 71 | with: 72 | branch: gh-pages 73 | folder: docs/_book 74 | clean: true 75 | single-commit: true 76 | dry-run: ${{ (github.event_name == 'workflow_dispatch' && (github.event.inputs.publish == 'false' || github.event.inputs.publish == false)) || false }} 77 | 78 | - name: Upload book folder for debug 79 | if: failure() 80 | uses: actions/upload-artifact@main 81 | with: 82 | name: book-dir 83 | path: docs 84 | -------------------------------------------------------------------------------- /man/check_site.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/check.R 3 | \name{check_site} 4 | \alias{check_site} 5 | \alias{check_config} 6 | \alias{check_gitignore} 7 | \alias{check_hugo} 8 | \alias{check_netlify} 9 | \alias{check_vercel} 10 | \alias{check_content} 11 | \title{Provide diagnostics for a website project} 12 | \usage{ 13 | check_site() 14 | 15 | check_config() 16 | 17 | check_gitignore() 18 | 19 | check_hugo() 20 | 21 | check_netlify() 22 | 23 | check_vercel() 24 | 25 | check_content() 26 | } 27 | \description{ 28 | The function \code{check_site()} runs all \code{check_*()} functions on this 29 | page against a website project. See \sQuote{Details} for what each 30 | \code{check_*()} function does. 31 | } 32 | \details{ 33 | \code{check_config()} checks the configuration file 34 | (\file{config.yaml} or \file{config.toml}) for settings such as 35 | \code{baseURL} and \code{ignoreFiles}. 36 | 37 | \code{check_gitignore()} checks if necessary files are incorrectly 38 | ignored in GIT. 39 | 40 | \code{check_hugo()} checks possible problems with the Hugo 41 | installation and version. 42 | 43 | \code{check_netlify()} checks the Hugo version specification and the 44 | publish directory in the Netlify config file \file{netlify.toml}. 45 | Specifically, it will check if the local Hugo version matches the version 46 | specified in \file{netlify.toml} (in the environment variable 47 | \var{HUGO_VERSION}), and if the \var{publish} setting in 48 | \file{netlify.toml} matches the \var{publishDir} setting in Hugo's config 49 | file (if it is set). 50 | 51 | \code{check_vercel()} checks if the Hugo version specified in 52 | \file{vercel.json} (if it exists) matches the Hugo version used in the 53 | current system. 54 | 55 | \code{check_content()} checks for possible problems in the content 56 | files. First, it checks for the validity of YAML metadata of all posts. 57 | Then it searches for posts with future dates and draft posts, and lists 58 | them if found (such posts appear in the local preview by default, but will 59 | be ignored by default when building the site). Then it checks for R 60 | Markdown posts that have not been rendered, or have output files older than 61 | the source files, and plain Markdown posts that have \file{.html} output 62 | files (which they should not have). At last, it detects \file{.html} files 63 | that seem to be generated by clicking the Knit button in RStudio with 64 | \pkg{blogdown} < v0.21. Such \file{.html} files should be deleted, since 65 | the Knit button only works with \pkg{blogdown} >= v0.21. 66 | } 67 | -------------------------------------------------------------------------------- /vignettes/articles/examples.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Example sites" 3 | resource_files: 4 | - images/ 5 | --- 6 | 7 | ```{r, include = FALSE, eval=FALSE} 8 | # To run to update the examples 9 | 10 | library(dplyr) 11 | library(purrr) 12 | library(rvest) 13 | 14 | # list of sites 15 | blogs <- tribble( 16 | ~ site, ~ source, 17 | "https://metadocencia.netlify.app/", "https://github.com/MetaDocencia/SitioWeb", 18 | "https://mariadermit.netlify.app/", "https://github.com/demar01/mariadermit", 19 | "https://shinydevseries.com/", "https://github.com/shinydevseries/shinydevseries_site", 20 | "https://r-podcast.org/", "https://github.com/rbind/r-podcast", 21 | "https://isabella-b.com/", "https://github.com/isabellabenabaye/isabella-b.com", 22 | # "https://robjhyndman.com/", "https://github.com/rbind/robjhyndman.com", 23 | "https://www.tidymodels.org/", "https://github.com/tidymodels/tidymodels.org", 24 | "https://livefreeordichotomize.com/", "https://github.com/LFOD/real-blog", 25 | "https://hugo-apero-docs.netlify.app/", "https://github.com/hugo-apero/hugo-apero-docs", 26 | "https://prose.yihui.org/", "https://github.com/yihui/hugo-prose/tree/master/exampleSite" 27 | ) 28 | 29 | # get the title 30 | blogs <- blogs %>% 31 | mutate( 32 | title = map_chr(site, ~{ 33 | read_html(.x) %>% 34 | html_node("title") %>% 35 | html_text() 36 | }), 37 | title = stringr::str_trim(title) 38 | ) 39 | 40 | # takes screenshot 41 | blogs <- blogs %>% 42 | mutate( 43 | img = xfun::with_ext( 44 | paste("images", urltools::domain(blogs$site), sep = "/"), "png") 45 | ) 46 | 47 | # export to YAML 48 | blogs %>% 49 | rename(href = "site") %>% 50 | mutate(showcase = TRUE) %>% 51 | purrr::pmap(purrr::lift_ld(as.list)) %>% 52 | yaml::write_yaml("examples.yml") 53 | ``` 54 | 55 | ```{r, include = FALSE} 56 | # we use as few new dependencies as possible 57 | blogs <- rmarkdown:::yaml_load_file("examples.yml") 58 | blogs <- do.call(rbind, lapply(blogs, function(x) data.frame(site = x$href, img = x$img))) 59 | # remotes::install_github("rstudio/webshot2") 60 | img_exists <- file.exists(blogs$img) 61 | if (any(!img_exists)) { 62 | need_screenshot <- blogs[!img_exists, ] 63 | purrr::pwalk(need_screenshot, function(site, img) { 64 | message("Screenshoting ", site) 65 | res <- webshot2::webshot(site, img, cliprect = "viewport") 66 | # optimize image - require optipng 67 | webshot2::shrink(res) 68 | }) 69 | } 70 | ``` 71 | 72 | The examples below illustrate the use of **blogdown** for making websites and blogs. You can also find a list of examples at 73 | 74 | ```{r, echo=FALSE} 75 | quillt::examples(yml = "examples.yml") 76 | ``` 77 | -------------------------------------------------------------------------------- /R/site.R: -------------------------------------------------------------------------------- 1 | blogdown_site = function(input, ...) { 2 | # set the site root dir 3 | opts$set(site_root = input) 4 | 5 | # start serving the site when a blogdown project is opened in RStudio 6 | if (interactive() && get_option('blogdown.serve_site.startup', FALSE)) try({ 7 | if (!isTRUE(opts$get('startup'))) { 8 | rstudioapi::sendToConsole('blogdown:::preview_site(startup = TRUE)') 9 | opts$set(startup = TRUE) # don't send the above code again in this session 10 | } 11 | }) 12 | 13 | output_dir = publish_dir() 14 | render = function(input_file, output_format, envir, quiet, ...) { 15 | # input_file is NULL when render the whole site, and is a file path when 16 | # rendering a single file (by clicking the Knit button) 17 | if (!is.null(input_file)) xfun::in_dir(input, { 18 | # set a global option 19 | opts$set(render_one = TRUE); on.exit(opts$set(render_one = NULL), add = TRUE) 20 | input_file = rel_path(input_file) 21 | # when knitting a file not in the project root, RStudio starts R from the 22 | # dir of the file instead of the root, hence .Rprofile is ignored (#562) 23 | if (dirname(input_file) != '.') source_profile(input, globalenv()) 24 | # only build R Markdown files (no need to build plain .md files) 25 | if (grepl(rmd_pattern, input_file)) 26 | build_site(TRUE, run_hugo = FALSE, build_rmd = input_file) 27 | # run serve_site() to preview the site if the server has not been started 28 | if (get_option('blogdown.knit.serve_site', Sys.getenv('BLOGDOWN_SERVING_DIR') == '')) { 29 | if (interactive()) preview_site() else tryCatch( 30 | rstudioapi::sendToConsole('blogdown:::preview_site()', echo = FALSE), 31 | error = function(e) {} 32 | ) 33 | } 34 | }) else { 35 | build_site(relativeURLs = if (parent_call('rsconnect::deploySite')) TRUE) 36 | if (!quiet) message( 37 | "\n==> The site has been generated to the directory '", output_dir, "'.\n\n", 38 | "** Note that normally you cannot just open the .html files in this directory ", 39 | "to view them in a browser. This directory need to be served before you can ", 40 | "preview web pages correctly (e.g., you may deploy the folder to a web server). ", 41 | "Alternatively, blogdown::serve_site() gives you a local preview of the site.\n" 42 | ) 43 | } 44 | } 45 | 46 | # return site generator 47 | list( 48 | name = basename(getwd()), 49 | output_dir = output_dir, 50 | render = render, 51 | subdirs = TRUE, 52 | clean = function() { 53 | x = c('blogdown', output_dir, clean_targets()) 54 | x[file.exists(x)] 55 | } 56 | ) 57 | } 58 | 59 | clean_targets = function() { 60 | rmds = list_rmds() 61 | files = by_products(rmds, c('.html', '.markdown')) 62 | c(files, 'static/rmarkdown-libs', list_files( 63 | 'static', '.+_files$', include.dirs = TRUE 64 | )) 65 | } 66 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Type: Package 2 | Package: blogdown 3 | Title: Create Blogs and Websites with R Markdown 4 | Version: 1.22.1 5 | Authors@R: c( 6 | person("Yihui", "Xie", role = c("aut", "cre"), email = "xie@yihui.name", comment = c(ORCID = "0000-0003-0645-5666")), 7 | person("Christophe", "Dervieux", role = "aut", email = "cderv@posit.co", comment = c(ORCID = "0000-0003-4474-2498")), 8 | person("Alison", "Presmanes Hill", role = "aut", comment = c(ORCID = "0000-0002-8082-1890")), 9 | # Contributors, ordered alphabetically 10 | person("Amber", "Thomas", role = "ctb"), 11 | person("Beilei", "Bian", role = "ctb"), 12 | person("Brandon ", "Greenwell", role = "ctb"), 13 | person("Brian", "Barkley", role = "ctb"), 14 | person("Deependra", "Dhakal", role = "ctb"), 15 | person("Eric", "Nantz", role = "ctb"), 16 | person("Forest", "Fang", role = "ctb"), 17 | person("Garrick", "Aden-Buie", role = "ctb"), 18 | person("Hiroaki", "Yutani", role = "ctb"), 19 | person("Ian", "Lyttle", role = "ctb"), 20 | person("Jake", "Barlow", role = "ctb"), 21 | person("James", "Balamuta", role = "ctb"), 22 | person("JJ", "Allaire", role = "ctb"), 23 | person("Jon", "Calder", role = "ctb"), 24 | person("Jozef", "Hajnala", role = "ctb"), 25 | person("Juan Manuel", "Vazquez", role = "ctb"), 26 | person("Kevin", "Ushey", role = "ctb"), 27 | person("Leonardo", "Collado-Torres", role = "ctb"), 28 | person("Maëlle", "Salmon", role = "ctb"), 29 | person("Maria Paula", "Caldas", role = "ctb"), 30 | person("Nicolas", "Roelandt", role = "ctb"), 31 | person("Oliver", "Madsen", role = "ctb", email = "oliver.p.madsen@gmail.com"), 32 | person("Raniere", "Silva", role = "ctb"), 33 | person("TC", "Zhang", role = "ctb"), 34 | person("Xianying", "Tan", role = "ctb"), 35 | person(given = "Posit Software, PBC", role = c("cph", "fnd")), 36 | person() 37 | ) 38 | Description: Write blog posts and web pages in R Markdown. This package 39 | supports the static site generator 'Hugo' () best, 40 | and it also supports 'Jekyll' () and 'Hexo' 41 | (). 42 | License: GPL-3 43 | URL: https://github.com/rstudio/blogdown, 44 | https://pkgs.rstudio.com/blogdown/ 45 | BugReports: https://github.com/rstudio/blogdown/issues 46 | Depends: R (>= 3.5.0) 47 | Imports: 48 | bookdown (>= 0.22), 49 | htmltools, 50 | httpuv (>= 1.4.0), 51 | jsonlite, 52 | knitr (>= 1.25), 53 | later, 54 | rmarkdown (>= 2.8), 55 | servr (>= 0.21), 56 | xfun (>= 0.34), 57 | yaml (>= 2.1.19) 58 | Suggests: 59 | miniUI, 60 | processx, 61 | rstudioapi, 62 | shiny, 63 | stringr, 64 | testit, 65 | tools, 66 | whoami 67 | Config/Needs/website: pkgdown, tidyverse/tidytemplate, rstudio/quillt, rstudio/webshot2 68 | Encoding: UTF-8 69 | RoxygenNote: 7.3.3 70 | SystemRequirements: Hugo () and Pandoc () 71 | -------------------------------------------------------------------------------- /inst/scripts/update_meta.R: -------------------------------------------------------------------------------- 1 | tags = htmltools::tags 2 | txt_input = function(..., width = '100%') shiny::textInput(..., width = width) 3 | sel_input = function(...) shiny::selectizeInput( 4 | ..., width = '100%', multiple = TRUE, options = list(create = TRUE) 5 | ) 6 | meta = blogdown:::collect_yaml() 7 | 8 | ctxt = rstudioapi::getSourceEditorContext(); txt = ctxt$contents 9 | if (length(ctxt$selection) == 0) stop( 10 | 'The document seems to be in the visual editor. Please put the cursor in YAML ', 11 | 'or switch to the source editor.', call. = FALSE 12 | ) 13 | res = blogdown:::split_yaml_body(txt); yml = res$yaml_list; rng = res$yaml_range 14 | if (length(rng) != 2) stop('Cannot find YAML metadata in the current document.', call. = FALSE) 15 | rstudioapi::setSelectionRanges(list(c(rng[1] + 1, 1, rng[2], 1))) 16 | slct = rstudioapi::getSourceEditorContext()$selection[[1]] 17 | 18 | if (length(yml) == 0) yml = list() 19 | yml = blogdown:::filter_list(yml) 20 | if (is.null(yml[['title']])) yml$title = '' 21 | if (is.null(yml[['author']])) yml$author = blogdown:::get_author() 22 | if (is.null(yml[['date']])) yml$date = Sys.Date() 23 | 24 | shiny::runGadget( 25 | miniUI::miniPage(miniUI::miniContentPanel( 26 | txt_input('title', 'Title', yml[['title']], placeholder = 'Post Title'), 27 | shiny::fillRow( 28 | txt_input('author', 'Author', yml[['author']], width = '99%'), 29 | shiny::dateInput('date', 'Date', yml[['date']], width = '99%'), 30 | height = '80px' 31 | ), 32 | shiny::checkboxInput( 33 | 'rename', 'Rename file if the date is changed', 34 | blogdown:::get_option('blogdown.rename_file', FALSE) 35 | ), 36 | sel_input( 37 | 'cat', 'Categories', blogdown:::sort2(unique(c(yml[['categories']], meta$categories))), 38 | selected = yml[['categories']] 39 | ), 40 | sel_input( 41 | 'tag', 'Tags', blogdown:::sort2(unique(c(yml[['tags']], meta$tags))), 42 | selected = yml[['tags']] 43 | ), 44 | shiny::fillRow(tags$div(), height = '20px'), 45 | miniUI::gadgetTitleBar(NULL) 46 | )), 47 | server = function(input, output) { 48 | shiny::observeEvent(input$done, { 49 | seq_keys = Filter(function(key) { 50 | identical(attr(yml[[key]], 'yml_type'), 'seq') 51 | }, names(yml)) 52 | seq_keys = unique(c(seq_keys, 'categories', 'tags')) 53 | 54 | res = list( 55 | title = input$title, author = input$author, date = format(input$date), 56 | categories = input$cat, tags = input$tag 57 | ) 58 | yml = c(res, yml[setdiff(names(yml), names(res))]) 59 | for (i in seq_keys) yml[[i]] = if (length(yml[[i]]) > 0) as.list(yml[[i]]) 60 | if (!blogdown:::get_option('blogdown.yaml.empty', TRUE)) yml = blogdown:::filter_list(yml) 61 | rstudioapi::modifyRange( 62 | slct$range, blogdown:::as.yaml(yml, .trim_ws = FALSE) 63 | ) 64 | if (input$rename) { 65 | rstudioapi::documentSave() 66 | p = ctxt$path; p2 = blogdown:::date_filename(p, res$date, replace = TRUE) 67 | b = if (basename(p) == basename(p2)) { 68 | file.rename(dirname(p), dirname(p2)) 69 | } else file.rename(p, p2) 70 | if (b) rstudioapi::navigateToFile(p2) 71 | } 72 | shiny::stopApp() 73 | }) 74 | shiny::observeEvent(input$cancel, { 75 | shiny::stopApp() 76 | }) 77 | }, 78 | stopOnCancel = FALSE, 79 | viewer = shiny::dialogViewer('Update YAML metadata', 500, 450) 80 | ) 81 | -------------------------------------------------------------------------------- /man/shortcode.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/hugo.R 3 | \name{shortcode} 4 | \alias{shortcode} 5 | \alias{shortcode_html} 6 | \alias{shortcodes} 7 | \alias{shortcode_open} 8 | \alias{shortcode_close} 9 | \title{Helper functions to write Hugo shortcodes using the R syntax} 10 | \usage{ 11 | shortcode(.name, ..., .content = NULL, .type = "markdown") 12 | 13 | shortcode_html(...) 14 | 15 | shortcodes(..., .sep = "\\n") 16 | 17 | shortcode_open(...) 18 | 19 | shortcode_close(...) 20 | } 21 | \arguments{ 22 | \item{.name}{The name of the shortcode.} 23 | 24 | \item{...}{All arguments of the shortcode (either all named, or all unnamed). 25 | The \code{...} arguments of all other functions are passed to 26 | \code{shortcode()}.} 27 | 28 | \item{.content}{The inner content for the shortcode.} 29 | 30 | \item{.type}{The type of the shortcode: \code{markdown} or \code{html}.} 31 | 32 | \item{.sep}{The separator between two shortcodes (by default, a newline).} 33 | } 34 | \value{ 35 | A character string wrapped in \code{htmltools::HTML()}; 36 | \code{shortcode()} returns a string of the form \code{{{\% name args \%}}}, 37 | and \code{shortcode_html()} returns \code{{{< name args >}}}. 38 | } 39 | \description{ 40 | These functions return Hugo shortcodes with the shortcode name and arguments 41 | you specify. The closing shortcode will be added only if the inner content is 42 | not empty. The function \code{shortcode_html()} is essentially 43 | \code{shortcode(.type = 'html')}. The function \code{shortcodes()} is a 44 | vectorized version of \code{shortcode()}. The paired functions 45 | \code{shortcode_open()} and \code{shortcode_close()} provide an alternative 46 | method to open and close shortcodes, which allows inner content be processed 47 | safely by Pandoc (e.g., citation keys in the content). 48 | } 49 | \details{ 50 | These functions can be used in either \pkg{knitr} inline R expressions or 51 | code chunks. The returned character string is wrapped in 52 | \code{htmltools::\link[htmltools]{HTML}()}, so \pkg{rmarkdown} will protect 53 | it from the Pandoc conversion. You cannot simply write \code{{{< shortcode 54 | >}}} in R Markdown, because Pandoc is not aware of Hugo shortcodes, and may 55 | convert special characters so that Hugo can no longer recognize the 56 | shortcodes (e.g. \code{<} will be converted to \code{<}). 57 | 58 | If your document is pure Markdown, you can use the Hugo syntax to write 59 | shortcodes, and there is no need to call these R functions. 60 | } 61 | \note{ 62 | Since Hugo v0.60, Hugo has switched its default Markdown rendering 63 | engine to Goldmark. One consequence is that shortcodes may fail to render. 64 | You may enable the \code{unsafe} option in the configuration file: 65 | \url{https://gohugo.io/getting-started/configuration-markup/#goldmark}. 66 | } 67 | \examples{ 68 | library(blogdown) 69 | 70 | shortcode("tweet", user = "SanDiegoZoo", id = "1453110110599868418") 71 | # multiple tweets (id's are fake) 72 | shortcodes("tweet", user = "SanDiegoZoo", id = as.character(1:5)) 73 | shortcode("figure", src = "/images/foo.png", alt = "A nice figure") 74 | shortcode("highlight", "bash", .content = "echo hello world;") 75 | 76 | shortcode_html("myshortcode", .content = "My shortcode.") 77 | 78 | shortcode_open("figure", src = "/images/foo.png") 79 | # This inner text will be *processed* by Pandoc, @Smith2006 80 | shortcode_close("figure") 81 | } 82 | \references{ 83 | \url{https://gohugo.io/extras/shortcodes/} 84 | } 85 | -------------------------------------------------------------------------------- /R/format.R: -------------------------------------------------------------------------------- 1 | #' An R Markdown output format for \pkg{blogdown} web pages 2 | #' 3 | #' This function is a simple wrapper of [bookdown::html_document2()] with 4 | #' different default arguments, and more importantly, a special HTML template 5 | #' designed only for \pkg{blogdown} to render R Markdown to HTML pages that can 6 | #' be processed by Hugo. 7 | #' 8 | #' The HTML output is not a complete HTML document, and only meaningful to 9 | #' \pkg{blogdown} (it will be post-processed to render valid HTML pages). The 10 | #' only purpose of this output format is for users to change options in YAML. 11 | #' 12 | #' The fact that it is based on \pkg{bookdown} means most \pkg{bookdown} 13 | #' features are supported, such as numbering and cross-referencing 14 | #' figures/tables. 15 | #' 16 | #' @param ...,number_sections,self_contained,highlight,template,pandoc_args 17 | #' Arguments passed to `bookdown::html_document2()` (note the option `theme` 18 | #' is not supported and set to `NULL` internally, and when `template = NULL`, 19 | #' a default template in \pkg{blogdown} will be used). 20 | #' @param keep_md,pre_knit,post_processor Passed to [rmarkdown::output_format]. 21 | #' 22 | #' @note Do not use a custom template unless you understand how the default 23 | #' template actually works (see the \pkg{blogdown} book). 24 | #' 25 | #' The argument `highlight` does not support the value `"textmate"`, and the 26 | #' argument `template` does not support the value `"default"`. 27 | #' @references See Chapter 2 of the \pkg{bookdown} book for the Markdown syntax: 28 | #' . See the \pkg{blogdown} book for full 29 | #' details: . 30 | #' @export 31 | #' @md 32 | html_page = function( 33 | ..., number_sections = FALSE, self_contained = FALSE, highlight = NULL, 34 | template = NULL, pandoc_args = c('-M', 'link-citations=true', '--preserve-tabs'), 35 | keep_md = FALSE, pre_knit = NULL, post_processor = NULL 36 | ) { 37 | if (identical(template, 'default')) stop( 38 | 'blogdown::html_page() does not support template = "default"' 39 | ) 40 | if (identical(highlight, 'textmate')) stop( 41 | 'blogdown::html_page() does not support highlight = "textmate"' 42 | ) 43 | if (is.character(pre_knit)) 44 | pre_knit = eval(parse(text = pre_knit)) 45 | if (is.character(post <- post_processor)) 46 | post = eval(parse(text = post_processor)) 47 | post_processor = function(metadata, input, output, ...) { 48 | if (is.function(post)) output = post(metadata, input, output, ...) 49 | # the output .html file contains no YAML metadata; need to prepend from .md 50 | if (grepl('[.]html~', output) && file_exists(f <- with_ext(output, '.knit.md~'))) { 51 | prepend_yaml(f, output, callback = function(s) { 52 | if (!getOption('blogdown.draft.output', FALSE)) return(s) 53 | if (length(s) < 2 || length(grep('^draft: ', s)) > 0) return(s) 54 | append(s, 'draft: true', 1) 55 | }) 56 | } 57 | output 58 | } 59 | rmarkdown::output_format( 60 | knitr = NULL, 61 | pandoc = NULL, 62 | clean_supporting = self_contained, 63 | keep_md = keep_md, 64 | pre_knit = pre_knit, 65 | post_processor = post_processor, 66 | base_format = bookdown::html_document2( 67 | ..., number_sections = number_sections, theme = NULL, 68 | self_contained = self_contained, highlight = highlight, 69 | pandoc_args = pandoc_args, 70 | template = template %n% pkg_file('resources', 'template-minimal.html') 71 | ) 72 | ) 73 | } 74 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | destination: reference 2 | 3 | # website will be referenced on https://pkgs.rstudio.com/ 4 | # Open a PR in https://github.com/rstudio/pkgs.rstudio.com 5 | url: https://pkgs.rstudio.com/blogdown/ 6 | 7 | template: 8 | package: tidytemplate 9 | bootstrap: 5 10 | bslib: 11 | primary: "#096B72" 12 | navbar-background: "#e6f3fc" 13 | trailing_slash_redirect: true 14 | opengraph: 15 | image: 16 | src: https://bookdown.org/yihui/blogdown/images/logo.png 17 | alt: "blogdown package" 18 | twitter: 19 | creator: "@rstudio" 20 | card: summary 21 | 22 | home: 23 | links: 24 | - text: Learn more about R Markdown 25 | href: "https://rmarkdown.rstudio.com" 26 | 27 | # custom footer for rmarkdown ecosystem 28 | footer: 29 | structure: 30 | left: [rmd] 31 | right: [developed_by, p, built_with] 32 | components: 33 | p: "\n\n" 34 | rmd: | 35 | **blogdown** is a part of the **R Markdown** ecosystem of packages for creating 36 | computational documents in R.
    Learn more at 37 | [rmarkdown.rstudio.com](https://rmarkdown.rstudio.com/). 38 | 39 | # structure of website themed for R Markdown ecosystem 40 | navbar: 41 | structure: 42 | left: [intro, examples, articles, reference, news] 43 | components: 44 | examples: 45 | text: Examples 46 | href: articles/examples.html 47 | 48 | # Add articles menu using 49 | # https://pkgdown.r-lib.org/dev/reference/build_articles.html#index-and-navbar 50 | articles: 51 | - title: Get Started 52 | desc: | 53 | Start here to know how to learn **blogdown** 54 | contents: 55 | - blogdown 56 | - title: Example 57 | desc: Gallery of examples 58 | contents: 59 | - articles/examples 60 | 61 | news: 62 | releases: 63 | - text: "Version 1.6" 64 | href: https://yihui.org/en/2021/11/blogdown-v1-6/ 65 | - text: "Version 1.0" 66 | href: https://posit.co/blog/blogdown-v1-0/ 67 | - text: "Version 0.1" 68 | href: https://posit.co/blog/announcing-blogdown/ 69 | 70 | reference: 71 | - title: Output formats 72 | desc: > 73 | If you use files with the `.Rmd` extension, the default output format is 74 | `blogdown::html_page`, which uses Pandoc to render. You may also use files with the `.Rmarkdown` file extension, which 75 | may be knit to `.markdown` files to be processed by Hugo's markdown renderer. 76 | contents: 77 | - html_page 78 | 79 | - title: Creating new websites & content 80 | contents: 81 | - new_site 82 | - install_theme 83 | - new_post 84 | - new_content 85 | - -starts_with("hugo") 86 | 87 | - title: Previewing and rendering websites 88 | contents: 89 | - serve_site 90 | - build_site 91 | 92 | - title: Managing website configurations 93 | contents: 94 | - starts_with("config") 95 | 96 | - title: Checking blogdown projects 97 | contents: 98 | - starts_with("check") 99 | 100 | - title: Working with Hugo 101 | desc: > 102 | These functions are helpers to work with Hugo and its features. 103 | contents: 104 | - ends_with("hugo") 105 | - starts_with("hugo") 106 | - starts_with("shortcode") 107 | - bundle_site 108 | - find_yaml 109 | - read_toml 110 | - -starts_with("check_") 111 | 112 | - title: Helper functions 113 | desc: These functions are utility functions when working with blogdown. 114 | contents: 115 | - build_dir 116 | - clean_duplicates 117 | - dep_path 118 | - starts_with("filter") 119 | 120 | - title: The blogdown package 121 | desc: ~ 122 | contents: 123 | - blogdown-package 124 | -------------------------------------------------------------------------------- /vignettes/blogdown.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Learn blogdown" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Learn blogdown} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ## User Guide 11 | 12 | blogdown: Creating Websites with R Markdown 13 | 14 | Written by Yihui Xie, the package author, [blogdown: Creating Websites with R Markdown](https://bookdown.org/yihui/blogdown/) introduces the R package and how to use it. The book is published by Chapman & Hall/CRC, and you can read it online for free. 15 | 16 | The book is structured into 5 parts to guide the reader into the use of the R package **blogdown** to create and manage websites: 17 | 18 | ```{r, results='asis', echo = FALSE, eval = FALSE} 19 | # run this to update the content below 20 | xfun::pkg_attach2("xml2") 21 | html <- read_html("https://bookdown.org/yihui/blogdown/") 22 | chapters <- xml_find_all(html, "//li[@class='chapter']") 23 | first_level <- chapters[which(purrr::map_lgl(xml_attr(chapters, 'data-level'), ~ grepl('^\\d+$', .x)))] 24 | titles <- xml_text(xml_find_all(first_level, "a")) 25 | titles <- gsub("^(\\d+)", "\\1.", titles) 26 | titles <- gsub("^(.*) \\([*])$", "\\1", titles) 27 | url <- file.path("https://bookdown.org/yihui/blogdown", xml_attr(first_level, "data-path")) 28 | formatted <- sprintf("* [%s](%s)", titles, url) 29 | cat(formatted, sep = "\n") 30 | ``` 31 | 32 | 33 | * [1. Get Started](https://bookdown.org/yihui/blogdown/get-started.html) introduces the package and how to get started using it. This is the chapter every user is expected to read. 34 | * [2. Hugo](https://bookdown.org/yihui/blogdown/hugo.html) introduces [Hugo](https://gohugo.io), the static site generator on which blogdown is based. It will act as a guide to those who are just getting started with Hugo. 35 | * [3. Deployment](https://bookdown.org/yihui/blogdown/deployment.html) explains different methods to deploy your static website online. 36 | * [4. Migration](https://bookdown.org/yihui/blogdown/migration.html) explains how to migrate from other framework to a Hugo website. 37 | * [5. Other Generators](https://bookdown.org/yihui/blogdown/other-generators.html) explains how to use **blogdown** with a custom build method. 38 | 39 | 40 | **A note from the authors**: Some of the information and instructions in this book are now out of date because of changes to Hugo and the recent **blogdown** package. Most of the book is still valid but some chapters may have less accurate informations 41 | 42 | ## About R Markdown 43 | 44 | R Markdown: The Definitive Guide 45 | 46 | If you are new to **R Markdown**, we recommend you start with [R Markdown: The Definitive Guide](https://bookdown.org/yihui/rmarkdown/) to get an overview. [Part I](https://bookdown.org/yihui/rmarkdown/installation.html) introduces how to install the relevant packages, and provides an overview of R Markdown, including the possible output formats, the Markdown syntax, the R code chunk syntax, and how to use other languages in R Markdown. 47 | 48 | Next, the chapter on [_Websites_](https://bookdown.org/yihui/rmarkdown/websites.html) will help orient you to how the **blogdown** package allows you to use R Markdown to create websites. 49 | 50 | ## Going further with examples 51 | 52 | Look at the [Examples](articles/examples.html) page. 53 | -------------------------------------------------------------------------------- /docs/10-experience.Rmd: -------------------------------------------------------------------------------- 1 | # Personal Experience 2 | 3 | I started blogging at blogchina.com in 2005, moved to blog.com.cn, then MSN Space, and finally purchased my own domain `yihui.org` and a virtual host. I first used a PHP application named Bo-Blog, then switched to WordPress, and then Jekyll. Finally I moved to Hugo. Although I have moved several times, all my posts have been preserved, and you can still see my first post in Chinese in 2005. I often try my best not to introduce broken links (which lead to the 404 page) every time I change the backend of my website. When it is too hard to preserve the original links of certain pages, I will redirect the broken URLs to the new URLs. That is why it is important for your system to support redirections, and in particular, 301 redirections (Netlify does a nice job here). Here are some of my redirection rules: . For example, `http://yihui.org/en/feed/` was the RSS feed of my old WordPress and Jekyll blogs in English, and Hugo generates the RSS feed to `/en/index.xml` instead, so I need to redirect `/en/feed/` to `/en/index.xml`. 4 | 5 | Google has provided several tools to help you know more information about your website. For example, [Google Search Console](https://search.google.com/search-console) can show you which pages give users bad page experience on mobile and desktop. I use these tools frequently by myself. 6 | 7 | I firmly believe in the value of writing. Over the years, I have written more than 1000 posts in Chinese and English. Some are long, and most are short. The total size of these text files is about 5 Mb. In retrospect, most posts are probably not valuable to general readers (some are random thoughts, and some are my rants), but I feel I benefitted a lot from writing in two aspects: 8 | 9 | 1. If I sit down and focus on writing a small topic for a while, I often feel my thoughts will become clearer. A major difference between writing and talking is that you can always reorganize things and revise them when writing. I do not think writing on social media counts. 140 characters may well be thoughtful, but I feel there is so much chaos there. It is hard to lay out systematic thoughts only through short messages, and these quick messages are often quickly forgotten. 10 | 11 | 1. I know some bloggers are very much against comments, so they do not open comments to the public. I have not had a very negative experience with comments yet. On the contrary, I constantly find inspirations from comments. For example, [I was thinking](https://yihui.org/en/2013/04/travis-ci-for-r/) if it was possible to automatically check R packages on the cloud through Travis CI. At that time (April 2013), I believe not many people in the R community had started using Travis CI, although I'm not sure if I was the first person experimenting with this idea. I felt Travis CI could be promising, but it did not support R back then. Someone named Vincent Arel-Bundock (I still do not know him) told me a hack in a comment, which suddenly lit up my mind and I quickly figured out a solution. In October 2013, Craig Citro started more solid work on the R support on Travis CI. I do not know if he saw my blog post. Anyway, I think Travis CI has made substantial impact on R package developers, which is a great thing for the R community. 12 | 13 | Yet another relatively small benefit is that I often go to my own posts to learn some technical stuff that I have forgotten. For example, I find it difficult to remember the syntax of different types of zero-width assertions in Perl-like regular expressions: `(?=...)`, `(?!...)`, `(?<=...)`, and `(? New File -> R Markdown`. 34 | 35 | You are strongly recommended to go through the documentation of **knitr** chunk options and Pandoc's manual at least once to have an idea of all possibilities. The basics of Markdown are simple enough, but there are many less well-known features in Pandoc's Markdown, too. As we mentioned in Section \@ref(output-format), **blogdown**'s output format is based on **bookdown** [@R-bookdown], which contains several other Markdown extensions, such as numbered equations and theorem environments, and you need to read Chapter 2 of the **bookdown** book [@xie2016] to learn more about these features. 36 | 37 | You can find an R Markdown cheat sheet and a reference guide at https://posit.co/resources/cheatsheets/, which can be handy after you are more familiar with R Markdown. 38 | 39 | With R Markdown, you only need to maintain the source documents; all output pages can be automatically generated from source documents. This makes it much easier to maintain a website, especially when the website is related to data analysis or statistical computing and graphics. When the source code is updated (e.g., the model or data is changed), your web pages can be updated accordingly and automatically. There is no need to run the code separately and cut-and-paste again. Besides the convenience, you gain reproducibility at the same time. 40 | -------------------------------------------------------------------------------- /inst/scripts/new_post.R: -------------------------------------------------------------------------------- 1 | tags = htmltools::tags 2 | txt_input = function(..., width = '100%') shiny::textInput(..., width = width) 3 | sel_input = function(...) shiny::selectizeInput( 4 | ..., width = '98%', multiple = TRUE, options = list(create = TRUE) 5 | ) 6 | meta = blogdown:::collect_yaml() 7 | lang = blogdown:::get_lang() 8 | adir = blogdown:::archetypes() 9 | 10 | shiny::runGadget( 11 | miniUI::miniPage(miniUI::miniContentPanel( 12 | txt_input('title', 'Title', placeholder = 'Post Title'), 13 | shiny::fillRow( 14 | txt_input('author', 'Author', blogdown:::get_author(), width = '98%'), 15 | shiny::dateInput('date', 'Date', Sys.Date(), width = '98%'), 16 | shiny::selectizeInput( 17 | 'subdir', 'Subdirectory', blogdown:::get_subdirs(), 18 | selected = getOption('blogdown.subdir', 'post'), 19 | width = '98%', multiple = FALSE, 20 | options = list(create = TRUE, placeholder = '(optional)') 21 | ), 22 | height = '70px' 23 | ), 24 | shiny::fillRow( 25 | sel_input('cat', 'Categories', meta$categories), 26 | sel_input('tag', 'Tags', meta$tags), 27 | shiny::selectInput( 28 | 'kind', 'Archetype', width = '98%', 29 | choices = unique(c('', adir)) 30 | ), 31 | height = '70px' 32 | ), 33 | shiny::fillRow( 34 | txt_input('file', 'Filename', '', 'automatically generated (edit if you want)'), 35 | height = '70px' 36 | ), 37 | if (is.null(lang)) { 38 | shiny::fillRow(txt_input('slug', 'Slug', '', '(optional)'), height = '70px') 39 | } else { 40 | shiny::fillRow( 41 | txt_input('slug', 'Slug', '', '(optional)', width = '98%'), 42 | txt_input('lang', 'Language', lang, width = '98%'), 43 | height = '70px' 44 | ) 45 | }, 46 | shiny::fillRow( 47 | shiny::radioButtons( 48 | 'format', 'Format', inline = TRUE, 49 | c('Markdown' = '.md', 'R Markdown (.Rmd)' = '.Rmd', 'R Markdown (.Rmarkdown)' = '.Rmarkdown'), 50 | selected = getOption('blogdown.ext', '.md') 51 | ), 52 | height = '70px' 53 | ), 54 | miniUI::gadgetTitleBar(NULL) 55 | )), 56 | server = function(input, output, session) { 57 | empty_title = shiny::reactive(grepl('^\\s*$', input$title)) 58 | shiny::observe({ 59 | shiny::updateTextInput( 60 | session, 'slug', 61 | placeholder = if (empty_title()) '(optional)' else blogdown:::dash_filename(input$title) 62 | ) 63 | }) 64 | # update subdir in according to the title 65 | if (is.function(subdir_fun <- getOption('blogdown.subdir_fun'))) shiny::observe({ 66 | sub2 = subdir_fun(input$title) 67 | shiny::updateSelectizeInput(session, 'subdir', selected = sub2, choices = unique(c( 68 | sub2, blogdown:::get_subdirs() 69 | ))) 70 | }) 71 | shiny::observe({ 72 | # calculate file path 73 | if (grepl('^\\s*$', slug <- input$slug)) slug = blogdown:::dash_filename(input$title) 74 | shiny::updateTextInput( 75 | session, 'file', value = blogdown:::post_filename( 76 | slug, input$subdir, shiny::isolate(input$format), input$date, input$lang 77 | ) 78 | ) 79 | }) 80 | shiny::observeEvent(input$format, { 81 | f = input$file 82 | if (f != '') shiny::updateTextInput( 83 | session, 'file', value = xfun::with_ext(f, input$format) 84 | ) 85 | }, ignoreInit = TRUE) 86 | shiny::observeEvent(input$done, { 87 | if (grepl('^\\s*$', input$file)) return( 88 | warning('The filename is empty!', call. = FALSE) 89 | ) 90 | options(blogdown.author = input$author) # remember the author name 91 | blogdown::new_post( 92 | input$title, author = input$author, ext = input$format, 93 | categories = input$cat, tags = input$tag, 94 | file = gsub('[-[:space:]]+', '-', input$file), 95 | slug = if (input$slug != '') input$slug, subdir = input$subdir, 96 | date = input$date, kind = xfun::sans_ext(input$kind) 97 | ) 98 | shiny::stopApp() 99 | }) 100 | shiny::observeEvent(input$cancel, { 101 | shiny::stopApp() 102 | }) 103 | }, 104 | stopOnCancel = FALSE, viewer = shiny::dialogViewer('New Post', height = 500) 105 | ) 106 | -------------------------------------------------------------------------------- /man/build_site.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/render.R 3 | \name{build_site} 4 | \alias{build_site} 5 | \title{Build a website} 6 | \usage{ 7 | build_site(local = FALSE, run_hugo = TRUE, build_rmd = FALSE, ...) 8 | } 9 | \arguments{ 10 | \item{local}{Whether to build the website locally. This argument is passed to 11 | \code{\link{hugo_build}()}, and \code{local = TRUE} is mainly for serving 12 | the site locally via \code{\link{serve_site}()}.} 13 | 14 | \item{run_hugo}{Whether to run \code{hugo_build()} after R Markdown files are 15 | compiled.} 16 | 17 | \item{build_rmd}{Whether to (re)build R Markdown files. By default, they are 18 | not built. See \sQuote{Details} for how \code{build_rmd = TRUE} works. 19 | Alternatively, it can take a vector of file paths, which means these files 20 | are to be (re)built. Or you can provide a function that takes a vector of 21 | paths of all R Markdown files under the \file{content/} directory, and 22 | returns a vector of paths of files to be built, e.g., \code{build_rmd = 23 | blogdown::filter_timestamp}. A few aliases are currently provided for such 24 | functions: \code{build_rmd = 'newfile'} is equivalent to \code{build_rmd = 25 | blogdown::filter_newfile}, \code{build_rmd = 'timestamp'} is equivalent to 26 | \code{build_rmd = blogdown::filter_timestamp}, and \code{build_rmd = 27 | 'md5sum'} is equivalent to \code{build_rmd = blogdown::filter_md5sum}.} 28 | 29 | \item{...}{Other arguments to be passed to \code{\link{hugo_build}()}.} 30 | } 31 | \description{ 32 | Build the site through Hugo, and optionally (re)build R Markdown files. 33 | } 34 | \details{ 35 | You can use \code{\link{serve_site}()} to preview your website locally, and 36 | \code{build_site()} to build the site for publishing. However, if you use a 37 | web publishing service like Netlify, you do not need to build the site 38 | locally, but can build it on the cloud. See Section 1.7 of the \pkg{blogdown} 39 | book for more information: 40 | \url{https://bookdown.org/yihui/blogdown/workflow.html}. 41 | 42 | For R Markdown posts, there are a few possible rendering methods: \code{html} 43 | (the default), \code{markdown}, and \code{custom}. The method can be set in 44 | the global option \code{blogdown.method} (usually in the 45 | \file{\link{.Rprofile}} file), e.g., \code{options(blogdown.method = 46 | "custom")}. 47 | 48 | For the \code{html} method, \file{.Rmd} posts are rendered to \file{.html} 49 | via \code{rmarkdown::\link[rmarkdown]{render}()}, which means Markdown is 50 | processed through Pandoc. For the \code{markdown} method, \file{.Rmd} is 51 | rendered to \file{.md}, which will typically be rendered to HTML later by the 52 | site generator such as Hugo. 53 | 54 | For all rendering methods, a custom R script \file{R/build.R} will be 55 | executed if you have provided it under the root directory of the website 56 | (e.g. you can compile Rmd to Markdown through 57 | \code{knitr::\link[knitr]{knit}()} and build the site via 58 | \code{\link{hugo_cmd}()}). The \code{custom} method means it is entirely up 59 | to this R script how a website is rendered. The script is executed via 60 | command line \command{Rscript "R/build.R"}, which means it is executed in a 61 | separate R session. The value of the argument \code{local} is passed to the 62 | command line (you can retrieve the command-line arguments via 63 | \code{\link{commandArgs}(TRUE)}). For other rendering methods, the R script 64 | \file{R/build2.R} (if exists) will be executed after Hugo has built the site. 65 | This can be useful if you want to post-process the site. 66 | 67 | When \code{build_rmd = TRUE}, all Rmd files will be (re)built. You can set 68 | the global option \code{blogdown.files_filter} to a function to determine 69 | which Rmd files to build when \code{build_rmd = TRUE}. This function takes a 70 | vector of Rmd file paths, and should return a subset of these paths to be 71 | built. By default, \code{options(blogdown.files_filter = \link{identity}}. 72 | You can use \code{blogdown::\link{filter_newfile}}, which means to build new 73 | Rmd files that have not been built before, or 74 | \code{blogdown::\link{filter_timestamp}} to build Rmd files if their time 75 | stamps (modification time) are newer than their output files, or 76 | \code{blogdown::\link{filter_md5sum}}, which is more robust in determining if 77 | an Rmd file has been modified (hence needs to be rebuilt). 78 | } 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blogdown 2 | 3 | 4 | [![R-CMD-check](https://github.com/rstudio/blogdown/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/rstudio/blogdown/actions/workflows/R-CMD-check.yaml) 5 | [![CRAN status](https://www.r-pkg.org/badges/version/blogdown)](https://CRAN.R-project.org/package=blogdown) 6 | [![Codecov test coverage](https://codecov.io/gh/rstudio/blogdown/branch/main/graph/badge.svg)](https://app.codecov.io/gh/rstudio/blogdown?branch=main) 7 | 8 | 9 | The goal of the blogdown package is to provide a powerful and customizable website output format for [R Markdown](https://rmarkdown.rstudio.com/). Use dynamic R Markdown documents to build webpages featuring: 10 | 11 | + R code (or other programming languages that [knitr](https://yihui.org/knitr/) supports), 12 | 13 | + automatically rendered output such as graphics, tables, analysis results, and HTML widgets, and 14 | 15 | + technical writing elements such as citations, footnotes, and LaTeX math, enabled by the [bookdown package](https://pkgs.rstudio.com/bookdown/). 16 | 17 | By default, blogdown uses [Hugo](https://gohugo.io), a popular open-source static website generator, which provides a fast and flexible way to build your site content to be shared online. Other website generators like Jekyll and Hexo are also supported. 18 | 19 | A useful feature of blogdown sites, compared to other R Markdown-based [websites](https://bookdown.org/yihui/rmarkdown/rmarkdown-site.html), is that you may organize your website content (including R Markdown files) within subdirectories. This makes blogdown a good solution not just for blogging or sites about R — it can also be used to create general-purpose websites to communicate about data science, statistics, data visualization, programming, or education. 20 | 21 | ## Book 22 | 23 | blogdown: Creating Websites with R Markdown 24 | 25 | ## Installation 26 | 27 | You can install the package via CRAN as follows: 28 | 29 | ```r 30 | install.packages('blogdown') 31 | ``` 32 | 33 | If you want to use the development version of the **blogdown** package, you can install the package from GitHub via the [**remotes** package](https://remotes.r-lib.org): 34 | 35 | ```r 36 | remotes::install_github('rstudio/blogdown') 37 | ``` 38 | ## Usage 39 | 40 | You may create a new site via the function `blogdown::new_site()` under an _empty_ directory. It will create a skeleton site, download a Hugo theme from Github, add some sample content, launch a web browser and you will see the new site. The sample blog post `hello-world.Rmd` should be opened automatically, and you can edit it. The website will be automatically rebuilt and the page will be refreshed after you save the file. 41 | 42 | If you use RStudio, you can create a new RStudio project for your website from the menu `File -> New Project -> New Directory -> Website using blogdown`. 43 | 44 | The function `blogdown::serve_site()` may be the most frequently used function in this package. It builds the website, loads it into your web browser, and automatically refreshes the browser when you update the Markdown or R Markdown files. Do not use the command line `hugo server` to build or serve the site. It only understands plain Markdown files, and cannot build R Markdown. 45 | 46 | You may not be satisfied with the default site created from `new_site()`. There are two things you may want to do after your first successful experiment with **blogdown**: 47 | 48 | 1. Pick a Hugo theme that you like from https://themes.gohugo.io. All you need is its Github user and repository name, to be passed to the `theme` argument of `new_site()`. 49 | 2. Add more content (pages or posts), or migrate your existing website. 50 | 51 | ## Getting help 52 | 53 | There are two main places to get help: 54 | 55 | 1. The [RStudio community](https://community.rstudio.com/tags/c/R-Markdown/10/blogdown) is a friendly place to ask any questions about **blogdown**. Be sure to use the `blogdown` tag. 56 | 57 | 1. [Stack Overflow](https://stackoverflow.com/questions/tagged/blogdown) is a great source of answers to common **blogdown** questions. Use the tags [`[r][blogdown]`](https://stackoverflow.com/questions/tagged/blogdown+r) if you ask a question. 58 | 59 | ## Code of Conduct 60 | 61 | Please note that the blogdown project is released with a [Contributor Code of Conduct](https://pkgs.rstudio.com/blogdown/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. 62 | -------------------------------------------------------------------------------- /pkgdown/assets/ace-1.2.3/mode-r.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/mode/tex_highlight_rules",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/lang"),s=e("./text_highlight_rules").TextHighlightRules,o=function(e){e||(e="text"),this.$rules={start:[{token:"comment",regex:"%.*$"},{token:e,regex:"\\\\[$&%#\\{\\}]"},{token:"keyword",regex:"\\\\(?:documentclass|usepackage|newcounter|setcounter|addtocounter|value|arabic|stepcounter|newenvironment|renewenvironment|ref|vref|eqref|pageref|label|cite[a-zA-Z]*|tag|begin|end|bibitem)\\b",next:"nospell"},{token:"keyword",regex:"\\\\(?:[a-zA-z0-9]+|[^a-zA-z0-9])"},{token:"paren.keyword.operator",regex:"[[({]"},{token:"paren.keyword.operator",regex:"[\\])}]"},{token:e,regex:"\\s+"}],nospell:[{token:"comment",regex:"%.*$",next:"start"},{token:"nospell."+e,regex:"\\\\[$&%#\\{\\}]"},{token:"keyword",regex:"\\\\(?:documentclass|usepackage|newcounter|setcounter|addtocounter|value|arabic|stepcounter|newenvironment|renewenvironment|ref|vref|eqref|pageref|label|cite[a-zA-Z]*|tag|begin|end|bibitem)\\b"},{token:"keyword",regex:"\\\\(?:[a-zA-z0-9]+|[^a-zA-z0-9])",next:"start"},{token:"paren.keyword.operator",regex:"[[({]"},{token:"paren.keyword.operator",regex:"[\\])]"},{token:"paren.keyword.operator",regex:"}",next:"start"},{token:"nospell."+e,regex:"\\s+"},{token:"nospell."+e,regex:"\\w+"}]}};r.inherits(o,s),t.TexHighlightRules=o}),ace.define("ace/mode/r_highlight_rules",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text_highlight_rules","ace/mode/tex_highlight_rules"],function(e,t,n){var r=e("../lib/oop"),i=e("../lib/lang"),s=e("./text_highlight_rules").TextHighlightRules,o=e("./tex_highlight_rules").TexHighlightRules,u=function(){var e=i.arrayToMap("function|if|in|break|next|repeat|else|for|return|switch|while|try|tryCatch|stop|warning|require|library|attach|detach|source|setMethod|setGeneric|setGroupGeneric|setClass".split("|")),t=i.arrayToMap("NULL|NA|TRUE|FALSE|T|F|Inf|NaN|NA_integer_|NA_real_|NA_character_|NA_complex_".split("|"));this.$rules={start:[{token:"comment.sectionhead",regex:"#+(?!').*(?:----|====|####)\\s*$"},{token:"comment",regex:"#+'",next:"rd-start"},{token:"comment",regex:"#.*$"},{token:"string",regex:'["]',next:"qqstring"},{token:"string",regex:"[']",next:"qstring"},{token:"constant.numeric",regex:"0[xX][0-9a-fA-F]+[Li]?\\b"},{token:"constant.numeric",regex:"\\d+L\\b"},{token:"constant.numeric",regex:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b"},{token:"constant.numeric",regex:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b"},{token:"constant.language.boolean",regex:"(?:TRUE|FALSE|T|F)\\b"},{token:"identifier",regex:"`.*?`"},{onMatch:function(n){return e[n]?"keyword":t[n]?"constant.language":n=="..."||n.match(/^\.\.\d+$/)?"variable.language":"identifier"},regex:"[a-zA-Z.][a-zA-Z0-9._]*\\b"},{token:"keyword.operator",regex:"%%|>=|<=|==|!=|\\->|<\\-|\\|\\||&&|=|\\+|\\-|\\*|/|\\^|>|<|!|&|\\||~|\\$|:"},{token:"keyword.operator",regex:"%.*?%"},{token:"paren.keyword.operator",regex:"[[({]"},{token:"paren.keyword.operator",regex:"[\\])}]"},{token:"text",regex:"\\s+"}],qqstring:[{token:"string",regex:'(?:(?:\\\\.)|(?:[^"\\\\]))*?"',next:"start"},{token:"string",regex:".+"}],qstring:[{token:"string",regex:"(?:(?:\\\\.)|(?:[^'\\\\]))*?'",next:"start"},{token:"string",regex:".+"}]};var n=(new o("comment")).getRules();for(var r=0;r) is a software engineer at Posit Software, PBC (). He earned his PhD from the Department of Statistics, Iowa State University. He is interested in interactive statistical graphics and statistical computing. As an active R user, he has authored several R packages, such as **knitr**, **bookdown**, **blogdown**, **xaringan**, **animation**, **DT**, **tufte**, **formatR**, **fun**, **mime**, **highr**, **servr**, and **Rd2roxygen**, among which the **animation** package won the 2009 John M. Chambers Statistical Software Award (ASA). He also co-authored a few other R packages, including **shiny**, **rmarkdown**, and **leaflet**. 12 | 13 | In 2006, he founded the Capital of Statistics (), which has grown into a large online community on statistics in China. He initiated the Chinese R conference in 2008, and has been involved in organizing R conferences in China since then. During his PhD training at Iowa State University, he won the Vince Sposito Statistical Computing Award (2011) and the Snedecor Award (2012) in the Department of Statistics. 14 | 15 | He occasionally rants on Twitter (https://twitter.com/xieyihui), and most of the time you can find him on GitHub (https://github.com/yihui). 16 | 17 | He enjoys spicy food as much as classical Chinese literature. 18 | 19 | ## Amber Thomas {-} 20 | 21 | Amber Thomas () is a data journalist and "maker" at the online publication of visual essays: The Pudding (). Her educational background, however, was in quite a different field altogether: marine biology. She has a bachelor's degree in marine biology and chemistry from Roger Williams University and a master's degree in marine sciences from the University of New England. Throughout her academic and professional career as a marine biologist, she realized that she had a love of data analysis, visualization, and storytelling and thus, she switched career paths to something a bit more data focused. 22 | 23 | While looking for work, she began conducting personal projects to expand her knowledge of R's inner workings. She decided to put all of her projects in a single place online (so that she could be discovered, naturally) and after lots of searching, she stumbled upon an early release of the **blogdown** package. She was hooked right away and spent a few days setting up her personal website and writing a tutorial on how she did it. You can find that tutorial and some of her other projects and musings on her blogdown site. 24 | 25 | When she is not crunching numbers and trying to stay on top of her email inbox, Amber is usually getting some fresh Seattle air or cuddling with her dog, Sherlock. If you are looking for her in the digital world, try . 26 | 27 | ## Alison Presmanes Hill {-} 28 | 29 | Alison () is a professor of pediatrics at Oregon Health and Science University's (OHSU) Center for Spoken Language Understanding in Portland, Oregon. Alison earned her PhD in developmental psychology with a concentration in quantitative methods from Vanderbilt University in 2008. Her current research focuses on developing better outcome measures to evaluate the impact of new treatments for children with autism and other neurodevelopmental disorders, using natural language processing and other computational methods. Alison is the author of numerous journal articles and book chapters, and her work has been funded by the National Institutes of Health, the Oregon Clinical and Translational Research Institute, and Autism Speaks. 30 | 31 | In addition to research, Alison teaches graduate-level courses in OHSU's Computer Science program (https://www.ohsu.edu/csee) on statistics, data science, and data visualization using R. She has also developed and led several R workshops and smaller team-based training sessions, and loves to train new "useRs." You can find some of her workshop and teaching materials on GitHub (https://github.com/apreshill) and, of course, on her **blogdown** site. 32 | 33 | Being a new mom, Alison's current favorite books are *The Circus Ship* and *Bats at the Ballgame*. She also does rousing renditions of most Emily Arrow songs (for private audiences only). 34 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at codeofconduct@posit.co. 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.1, available at 118 | . 119 | 120 | Community Impact Guidelines were inspired by 121 | [Mozilla's code of conduct enforcement ladder][https://github.com/mozilla/inclusion]. 122 | 123 | For answers to common questions about this code of conduct, see the FAQ at 124 | . Translations are available at . 125 | 126 | [homepage]: https://www.contributor-covenant.org 127 | -------------------------------------------------------------------------------- /docs/08-domain-name.Rmd: -------------------------------------------------------------------------------- 1 | # Domain Name 2 | 3 | While you can use the free subdomain names\index{Domain Name} like those provided by GitHub or Netlify, it may be a better idea to own a domain name of your own. The cost of an apex domain is minimal (typically the yearly cost is about US$10), and you will enter a much richer world after you purchase a domain name. For example, you are free to point your domain to any web servers, you can create as many subdomain names as you want, and you can even set up your own email accounts using the domain or subdomains. In this appendix, we will explain some basic concepts of domain names, and mention a few (free) services to help you configure your domain name. 4 | 5 | Before we dive into the details, we want to outline the big picture of how a URL works in your web browser. Suppose you typed or clicked a link `http://www.example.com/foo/index.html` in your web browser. What happens behind the scenes before you see the actual web page? 6 | 7 | First, the domain name has to be resolved through the nameservers associated with it. A nameserver knows the DNS (Domain Name System) records of a domain. Typically it will look up the "A records" to point the domain to the IP address of a web server. There are several other types of DNS records, and we will explain them later. Once the web server is reached, the server will look for the file `foo/index.html` under a directory associated with the domain name, and return its content in the response. That is basically how you can see a web page. 8 | 9 | ## Registration 10 | 11 | You can purchase a domain name from many domain name registrars. To stay neutral, we are not going to make recommendations here. You can use your search engine to find a registrar by yourself, or ask your friends for recommendations. However, we would like to remind you of a few things that you should pay attention to when looking for a domain name registrar: 12 | 13 | - You should have the freedom to transfer your domain from the current registrar to other registrars, i.e., they should not lock you in their system. To transfer a domain name, you should be given a code known as the "Transfer Auth Code" or "Auth Code" or "Transfer Key" or something like that. 14 | 15 | - You should be able to customize the nameservers (see Section \@ref(nameservers)) of your domain. By default, each registrar will assign their own nameservers to you, and these nameservers typically work very well. However, there are some special nameservers that provide services more than just DNS records, and you may be interested in using them. 16 | 17 | - Other people can freely look up your personal information, such as your email or postal address, after you register a domain and submit this information to the registrar. This is called the "WHOIS Lookup." You may want to protect your privacy, but your registrar may require an extra payment. 18 | 19 | ## Nameservers 20 | 21 | The main reason why we need nameservers\index{Nameservers} is that we want to use domains instead of IP addresses, although a domain is not strictly necessary for you to be able to access a website. You could use the IP address if you have your own server with a public IP, but there are many problems with this approach. For example, IP addresses are limited (in particular IPv4), not easy to memorize, and you can only host one website per IP address (without using other ports). 22 | 23 | A nameserver is an engine that directs the DNS records of your domain. The most common DNS record is the A record, which maps a domain to an IP address, so that the hosting server can be found via its IP address when a website is accessed through a domain. We will introduce two more types of DNS records in Section \@ref(dns-records): CNAME and MX records. 24 | 25 | In most cases, the default nameservers provided by your domain registrar should suffice, but there is a special technology missing in most nameservers: CNAME flattening. You only need this technology if you want to set a CNAME record for your apex domain. The only use case to my knowledge is when you host your website via Netlify, but want to use the apex domain instead of the `www` subdomain, e.g., you want to use `example.com` instead of `www.example.com`. To make use of this technology, you could consider [Cloudflare,](https://www.cloudflare.com) which provides this DNS feature for free. Basically, all you need to do is to point the nameservers of your domain to the nameservers provided by Cloudflare (of the form `*.ns.cloudflare.com`). 26 | 27 | ## DNS records 28 | 29 | There are many types of DNS\index{DNS Records} records, and you may see a full list on [Wikipedia.](https://en.wikipedia.org/wiki/List_of_DNS_record_types) The most commonly used types may be A, CNAME, and MX records. Figure \@ref(fig:cloudflare-dns) shows a subset of DNS records of my domain `yihui.org` on Cloudflare, which may give you an idea of what DNS records look like. You may query DNS records using command-line tools such as [`dig`](https://en.wikipedia.org/wiki/Dig_(command)) or an app provided by Google: https://toolbox.googleapps.com/apps/dig/. 30 | 31 | ```{r cloudflare-dns, fig.cap='Some DNS records of the domain yihui.org on Cloudflare.', fig.align='center', out.width='100%', echo=FALSE} 32 | knitr::include_graphics('images/cloudflare-dns.png') 33 | ``` 34 | 35 | An apex domain can have any number of subdomains. You can set DNS records for the apex domain and any subdomains. You can see from Figure \@ref(fig:cloudflare-dns) that I have several subdomains, e.g., `slides.yihui.org` and `xran.yihui.org`. 36 | 37 | As we have mentioned, an A record points a domain or subdomain to an IP address of the host server. I did not use any A records for my domains since all services I use, such as GitHub Pages and Netlify, support CNAME records well. A CNAME\index{CNAME Record} record is an alias, pointing one domain to another domain. The advantage of using CNAME over A is that you do not have to tie a domain to a fixed IP address. For example, the CNAME record for `t.yihui.org` is `twitter-yihui.netlify.com`. The latter domain is provided by Netlify, and I do not need to know where they actually host the website. They are free to move the host of `twitter-yihui.netlify.com`, and I will not need to update my DNS record. Every time someone visits the website `t.yihui.org`, the web browser will route the traffic to the domain set in the CNAME record. Note that this is different from redirection, i.e., the URL `t.yihui.org` will not be explicitly redirected to `twitter-yihui.netlify.com` (you still see the former in the address bar of your browser). 38 | 39 | Normally, you can set any DNS records for the apex domain except CNAME, but I set a CNAME record for my apex domain `yihui.org`, and that is because Cloudflare supports CNAME flattening. For more information on this topic, you may read the post ["To WWW or not WWW,"](https://www.netlify.com/blog/2017/02/28/to-www-or-not-www/) by Netlify. Personally, I prefer not using the subdomain `www.yihui.org` to keep my URLs short, so I set a CNAME record for both the apex domain `yihui.org` and the `www` subdomain, and Netlify will automatically redirect the `www` subdomain to the apex domain. That said, if you are a beginner, it may be a little easier to configure and use the `www` subdomain, as suggested by Netlify. Note `www` is a conventional subdomain that sounds like an apex domain, but really is not; you can follow this convention or not as you wish. 40 | 41 | For email services, I was an early enough ["netizen",](https://en.wikipedia.org/wiki/Netizen) and when I registered my domain name, Google was still offering free email services to custom domain owners. That is how I can have a custom mailbox `xie@yihui.org`. Now you will have to pay for [G Suite.](https://gsuite.google.com) In Figure \@ref(fig:cloudflare-dns) you can see I have set some MX (stands for "mail exchange") records that point to some Google mail servers. Of course, Google is not the only possible choice when it comes to custom mailboxes. [Migadu](https://www.migadu.com) claims to be the "most affordable email hosting." You may try its free plan and see if you like it. Unless you are going to use your custom mailbox extensively and for professional purposes, the free plan may suffice. In fact, you may create an alias address on Migadu to forward emails to your other email accounts (such as Gmail) if you do not care about an actual custom mailbox. Migadu has provided detailed instructions on how to set the MX records for your domain. 42 | -------------------------------------------------------------------------------- /man/hugo_cmd.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/hugo.R 3 | \name{hugo_cmd} 4 | \alias{hugo_cmd} 5 | \alias{hugo_version} 6 | \alias{hugo_available} 7 | \alias{hugo_build} 8 | \alias{new_site} 9 | \alias{new_content} 10 | \alias{new_post} 11 | \alias{hugo_convert} 12 | \alias{hugo_server} 13 | \title{Run Hugo commands} 14 | \usage{ 15 | hugo_cmd(...) 16 | 17 | hugo_version() 18 | 19 | hugo_available(version = "0.0.0", exact = FALSE) 20 | 21 | hugo_build( 22 | local = FALSE, 23 | args = getOption("blogdown.hugo.args"), 24 | baseURL = NULL, 25 | relativeURLs = NULL 26 | ) 27 | 28 | new_site( 29 | dir = ".", 30 | force = NA, 31 | install_hugo = TRUE, 32 | format = "yaml", 33 | sample = TRUE, 34 | theme = "yihui/hugo-lithium", 35 | hostname = "github.com", 36 | theme_example = TRUE, 37 | empty_dirs = FALSE, 38 | to_yaml = TRUE, 39 | netlify = TRUE, 40 | .Rprofile = TRUE, 41 | serve = if (interactive()) "ask" else FALSE 42 | ) 43 | 44 | new_content(path, kind = "", open = interactive()) 45 | 46 | new_post( 47 | title, 48 | kind = "", 49 | open = interactive(), 50 | author = getOption("blogdown.author"), 51 | categories = NULL, 52 | tags = NULL, 53 | date = Sys.Date(), 54 | time = getOption("blogdown.time", FALSE), 55 | file = NULL, 56 | slug = NULL, 57 | title_case = getOption("blogdown.title_case"), 58 | subdir = getOption("blogdown.subdir", "post"), 59 | ext = getOption("blogdown.ext", ".md") 60 | ) 61 | 62 | hugo_convert(to = c("YAML", "TOML", "JSON"), unsafe = FALSE, ...) 63 | 64 | hugo_server(host, port) 65 | } 66 | \arguments{ 67 | \item{...}{Arguments to be passed to \code{system2('hugo', ...)}, e.g. 68 | \code{new_content(path)} is basically \code{hugo_cmd(c('new', path))} (i.e. 69 | run the command \command{hugo new path}).} 70 | 71 | \item{version}{A version number.} 72 | 73 | \item{exact}{If \code{FALSE}, check if the current Hugo version is equal to 74 | or higher than the specified \code{version}. If \code{TRUE}, check if the 75 | exact version is available.} 76 | 77 | \item{local}{Whether to build the site for local preview (if \code{TRUE}, all 78 | drafts and future posts will also be built).} 79 | 80 | \item{args}{A character vector of command-line arguments to be passed to 81 | \command{hugo}, e.g., \code{c("--minify", "--quiet")}.} 82 | 83 | \item{baseURL, relativeURLs}{Custom values of \code{baseURL} and 84 | \code{relativeURLs} to override Hugo's default and the settings in the 85 | site's config file.} 86 | 87 | \item{dir}{The directory of the new site.} 88 | 89 | \item{force}{Whether to create the site in a directory even if it is not 90 | empty. By default, \code{force = TRUE} when the directory only contains 91 | hidden, RStudio project (\file{*.Rproj}), \file{LICENSE}, and/or 92 | \file{README} files.} 93 | 94 | \item{install_hugo}{Whether to install Hugo automatically if it is not found.} 95 | 96 | \item{format}{The format of the configuration file, e.g., \code{'yaml'} or 97 | \code{'toml'} (the value \code{TRUE} will be treated as \code{'yaml'}, and 98 | \code{FALSE} means \code{'toml'}). Note that the frontmatter of the new (R) 99 | Markdown file created by \code{new_content()} always uses YAML instead of 100 | TOML or JSON.} 101 | 102 | \item{sample}{Whether to add sample content. Hugo creates an empty site by 103 | default, but this function adds sample content by default.} 104 | 105 | \item{theme}{A Hugo theme on Github (a character string of the form 106 | \code{user/repo}, and you can optionally specify a GIT branch or tag name 107 | after \code{@}, i.e. \code{theme} can be of the form 108 | \code{user/repo@branch}). You can also specify a full URL to the zip file 109 | or tarball of the theme. If \code{theme = NA}, no themes will be installed, 110 | and you have to manually install a theme.} 111 | 112 | \item{hostname}{Where to find the theme. Defaults to \code{github.com}; 113 | specify if you wish to use an instance of GitHub Enterprise. You can also 114 | specify the full URL of the zip file or tarball in \code{theme}, in which 115 | case this argument is ignored.} 116 | 117 | \item{theme_example}{Whether to copy the example in the \file{exampleSite} 118 | directory if it exists in the theme. Not all themes provide example sites.} 119 | 120 | \item{empty_dirs}{Whether to keep the empty directories generated by Hugo.} 121 | 122 | \item{to_yaml}{Whether to convert the metadata of all posts to YAML.} 123 | 124 | \item{netlify}{Whether to create a Netlify config file \file{netlify.toml}.} 125 | 126 | \item{.Rprofile}{Whether to create a \file{.Rprofile} file. If \code{TRUE}, a 127 | sample \file{.Rprofile} will be created. It contains some global options, 128 | such as \code{options(blogdown.hugo.version)}, which makes sure you will 129 | use a specific version of Hugo for this site in the future.} 130 | 131 | \item{serve}{Whether to start a local server to serve the site. By default, 132 | this function will ask you in an interactive R session if you want to serve 133 | the site.} 134 | 135 | \item{path}{The path to the new file under the \file{content} directory.} 136 | 137 | \item{kind}{The content type to create, i.e., the Hugo archetype. If the 138 | archetype is a page bundle archetype, it should end with a slash, e.g., 139 | \code{post/}.} 140 | 141 | \item{open}{Whether to open the new file after creating it. By default, it is 142 | opened in an interactive R session.} 143 | 144 | \item{title}{The title of the post.} 145 | 146 | \item{author}{The author of the post.} 147 | 148 | \item{categories}{A character vector of category names.} 149 | 150 | \item{tags}{A character vector of tag names.} 151 | 152 | \item{date}{The date of the post.} 153 | 154 | \item{time}{Whether to include the time of the day in the \code{date} field 155 | of the post. If \code{TRUE}, the \code{date} will be of the format 156 | \samp{\%Y-\%m-\%dT\%H:\%M:\%S\%z} (e.g., \samp{2001-02-03T04:05:06-0700}). 157 | Alternatively, it can take a character string to be appended to the 158 | \code{date}. It can be important and helpful to include the time in the 159 | date of a post. For example, if your website is built on a server (such as 160 | Netlify or Vercel) and your local timezone is ahead of UTC, your local date 161 | may be a \emph{future} date on the server, and Hugo will not build future 162 | posts by default (unless you use the \command{-F} flag).} 163 | 164 | \item{file}{The filename of the post. By default, the filename will be 165 | automatically generated from the title by replacing non-alphanumeric 166 | characters with dashes, e.g. \code{title = 'Hello World'} may create a file 167 | \file{content/post/2016-12-28-hello-world.md}. The date of the form 168 | \code{YYYY-mm-dd} will be prepended if the filename does not start with a 169 | date.} 170 | 171 | \item{slug}{The slug of the post. By default (\code{NULL}), the slug is 172 | generated from the filename by removing the date and filename extension, 173 | e.g., if \code{file = 'post/2020-07-23-hi-there.md'}, \code{slug} will be 174 | \code{hi-there}. Set \code{slug = ''} if you do not want it.} 175 | 176 | \item{title_case}{A function to convert the title to title case. If 177 | \code{TRUE}, the function is \code{tools::\link[tools]{toTitleCase}()}). 178 | This argument is not limited to title case conversion. You can provide an 179 | arbitrary R function to convert the title.} 180 | 181 | \item{subdir}{If specified (not \code{NULL}), the post will be generated 182 | under a subdirectory under \file{content/}. It can be a nested subdirectory 183 | like \file{post/joe/}.} 184 | 185 | \item{ext}{The filename extension (e.g., \file{.md}, \file{.Rmd}, or 186 | \file{.Rmarkdown}). Ignored if \code{file} has been specified.} 187 | 188 | \item{to}{A format to convert to.} 189 | 190 | \item{unsafe}{Whether to enable unsafe operations, such as overwriting 191 | Markdown source documents. If you have backed up the website, or the 192 | website is under version control, you may try \code{unsafe = TRUE}.} 193 | 194 | \item{host, port}{The host IP address and port; see 195 | \code{servr::\link[servr]{server_config}()}.} 196 | } 197 | \description{ 198 | Wrapper functions to run Hugo commands via \code{\link{system2}('hugo', 199 | ...)}. 200 | } 201 | \section{Functions}{ 202 | \itemize{ 203 | \item \code{hugo_cmd()}: Run an arbitrary Hugo command. 204 | 205 | \item \code{hugo_version()}: Return the version number of Hugo if possible, which is 206 | extracted from the output of \code{hugo_cmd('version')}. 207 | 208 | \item \code{hugo_available()}: Check if Hugo of a certain version (or above if 209 | \code{exact = FALSE}) is available. 210 | 211 | \item \code{hugo_build()}: Build a plain Hugo website. Note that the function 212 | \code{\link{build_site}()} first compiles Rmd files, and then calls Hugo 213 | via \code{hugo_build()} to build the site. 214 | 215 | \item \code{new_site()}: Create a new site (skeleton) via \command{hugo new 216 | site}. The directory of the new site should be empty, 217 | 218 | \item \code{new_content()}: Create a new (R) Markdown file via \command{hugo new} 219 | (e.g. a post or a page). 220 | 221 | \item \code{new_post()}: A wrapper function to create a new post under the 222 | \file{content/post/} directory via \code{new_content()}. If your post will 223 | use R code chunks, you can set \code{ext = '.Rmd'} or the global option 224 | \code{options(blogdown.ext = '.Rmd')} in your \file{~/.Rprofile}. 225 | Similarly, you can set \code{options(blogdown.author = 'Your Name')} so 226 | that the author field is automatically filled out when creating a new post. 227 | 228 | \item \code{hugo_convert()}: A wrapper function to convert source content to 229 | different formats via \command{hugo convert}. 230 | 231 | \item \code{hugo_server()}: Start a Hugo server. 232 | 233 | }} 234 | \examples{ 235 | blogdown::hugo_available("1.2.3") 236 | if (interactive()) blogdown::new_site() 237 | } 238 | \references{ 239 | The full list of Hugo commands: \url{https://gohugo.io/commands}, 240 | and themes: \url{https://themes.gohugo.io}. 241 | } 242 | -------------------------------------------------------------------------------- /man/figures/logo.svg: -------------------------------------------------------------------------------- 1 | blogdown -------------------------------------------------------------------------------- /pkgdown/assets/ace-1.2.3/mode-xml.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/mode/xml_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(e){var t="[_:a-zA-Z\u00c0-\uffff][-_:.a-zA-Z0-9\u00c0-\uffff]*";this.$rules={start:[{token:"string.cdata.xml",regex:"<\\!\\[CDATA\\[",next:"cdata"},{token:["punctuation.xml-decl.xml","keyword.xml-decl.xml"],regex:"(<\\?)(xml)(?=[\\s])",next:"xml_decl",caseInsensitive:!0},{token:["punctuation.instruction.xml","keyword.instruction.xml"],regex:"(<\\?)("+t+")",next:"processing_instruction"},{token:"comment.xml",regex:"<\\!--",next:"comment"},{token:["xml-pe.doctype.xml","xml-pe.doctype.xml"],regex:"(<\\!)(DOCTYPE)(?=[\\s])",next:"doctype",caseInsensitive:!0},{include:"tag"},{token:"text.end-tag-open.xml",regex:"",next:"start"}],processing_instruction:[{token:"punctuation.instruction.xml",regex:"\\?>",next:"start"},{defaultToken:"instruction.xml"}],doctype:[{include:"whitespace"},{include:"string"},{token:"xml-pe.doctype.xml",regex:">",next:"start"},{token:"xml-pe.xml",regex:"[-_a-zA-Z0-9:]+"},{token:"punctuation.int-subset",regex:"\\[",push:"int_subset"}],int_subset:[{token:"text.xml",regex:"\\s+"},{token:"punctuation.int-subset.xml",regex:"]",next:"pop"},{token:["punctuation.markup-decl.xml","keyword.markup-decl.xml"],regex:"(<\\!)("+t+")",push:[{token:"text",regex:"\\s+"},{token:"punctuation.markup-decl.xml",regex:">",next:"pop"},{include:"string"}]}],cdata:[{token:"string.cdata.xml",regex:"\\]\\]>",next:"start"},{token:"text.xml",regex:"\\s+"},{token:"text.xml",regex:"(?:[^\\]]|\\](?!\\]>))+"}],comment:[{token:"comment.xml",regex:"-->",next:"start"},{defaultToken:"comment.xml"}],reference:[{token:"constant.language.escape.reference.xml",regex:"(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"}],attr_reference:[{token:"constant.language.escape.reference.attribute-value.xml",regex:"(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"}],tag:[{token:["meta.tag.punctuation.tag-open.xml","meta.tag.punctuation.end-tag-open.xml","meta.tag.tag-name.xml"],regex:"(?:(<)|(",next:"start"}]}],tag_whitespace:[{token:"text.tag-whitespace.xml",regex:"\\s+"}],whitespace:[{token:"text.whitespace.xml",regex:"\\s+"}],string:[{token:"string.xml",regex:"'",push:[{token:"string.xml",regex:"'",next:"pop"},{defaultToken:"string.xml"}]},{token:"string.xml",regex:'"',push:[{token:"string.xml",regex:'"',next:"pop"},{defaultToken:"string.xml"}]}],attributes:[{token:"entity.other.attribute-name.xml",regex:"(?:"+t+":)?"+t+""},{token:"keyword.operator.attribute-equals.xml",regex:"="},{include:"tag_whitespace"},{include:"attribute_value"}],attribute_value:[{token:"string.attribute-value.xml",regex:"'",push:[{token:"string.attribute-value.xml",regex:"'",next:"pop"},{include:"attr_reference"},{defaultToken:"string.attribute-value.xml"}]},{token:"string.attribute-value.xml",regex:'"',push:[{token:"string.attribute-value.xml",regex:'"',next:"pop"},{include:"attr_reference"},{defaultToken:"string.attribute-value.xml"}]}]},this.constructor===s&&this.normalizeRules()};(function(){this.embedTagRules=function(e,t,n){this.$rules.tag.unshift({token:["meta.tag.punctuation.tag-open.xml","meta.tag."+n+".tag-name.xml"],regex:"(<)("+n+"(?=\\s|>|$))",next:[{include:"attributes"},{token:"meta.tag.punctuation.tag-close.xml",regex:"/?>",next:t+"start"}]}),this.$rules[n+"-end"]=[{include:"attributes"},{token:"meta.tag.punctuation.tag-close.xml",regex:"/?>",next:"start",onMatch:function(e,t,n){return n.splice(0),this.token}}],this.embedRules(e,t,[{token:["meta.tag.punctuation.end-tag-open.xml","meta.tag."+n+".tag-name.xml"],regex:"(|$))",next:n+"-end"},{token:"string.cdata.xml",regex:"<\\!\\[CDATA\\["},{token:"string.cdata.xml",regex:"\\]\\]>"}])}}).call(i.prototype),r.inherits(s,i),t.XmlHighlightRules=s}),ace.define("ace/mode/behaviour/xml",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"],function(e,t,n){"use strict";function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}var r=e("../../lib/oop"),i=e("../behaviour").Behaviour,s=e("../../token_iterator").TokenIterator,o=e("../../lib/lang"),a=function(){this.add("string_dquotes","insertion",function(e,t,n,r,i){if(i=='"'||i=="'"){var o=i,a=r.doc.getTextRange(n.getSelectionRange());if(a!==""&&a!=="'"&&a!='"'&&n.getWrapBehavioursEnabled())return{text:o+a+o,selection:!1};var f=n.getCursorPosition(),l=r.doc.getLine(f.row),c=l.substring(f.column,f.column+1),h=new s(r,f.row,f.column),p=h.getCurrentToken();if(c==o&&(u(p,"attribute-value")||u(p,"string")))return{text:"",selection:[1,1]};p||(p=h.stepBackward());if(!p)return;while(u(p,"tag-whitespace")||u(p,"whitespace"))p=h.stepBackward();var d=!c||c.match(/\s/);if(u(p,"attribute-equals")&&(d||c==">")||u(p,"decl-attribute-equals")&&(d||c=="?"))return{text:o+o,selection:[1,1]}}}),this.add("string_dquotes","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&(s=='"'||s=="'")){var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u==s)return i.end.column++,i}}),this.add("autoclosing","insertion",function(e,t,n,r,i){if(i==">"){var o=n.getCursorPosition(),a=new s(r,o.row,o.column),f=a.getCurrentToken()||a.stepBackward();if(!f||!(u(f,"tag-name")||u(f,"tag-whitespace")||u(f,"attribute-name")||u(f,"attribute-equals")||u(f,"attribute-value")))return;if(u(f,"reference.attribute-value"))return;if(u(f,"attribute-value")){var l=f.value.charAt(0);if(l=='"'||l=="'"){var c=f.value.charAt(f.value.length-1),h=a.getCurrentTokenColumn()+f.value.length;if(h>o.column||h==o.column&&l!=c)return}}while(!u(f,"tag-name"))f=a.stepBackward();var p=a.getCurrentTokenRow(),d=a.getCurrentTokenColumn();if(u(a.stepBackward(),"end-tag-open"))return;var v=f.value;p==o.row&&(v=v.substring(0,o.column-d));if(this.voidElements.hasOwnProperty(v.toLowerCase()))return;return{text:">",selection:[1,1]}}}),this.add("autoindent","insertion",function(e,t,n,r,i){if(i=="\n"){var o=n.getCursorPosition(),u=r.getLine(o.row),a=new s(r,o.row,o.column),f=a.getCurrentToken();if(f&&f.type.indexOf("tag-close")!==-1){if(f.value=="/>")return;while(f&&f.type.indexOf("tag-name")===-1)f=a.stepBackward();if(!f)return;var l=f.value,c=a.getCurrentTokenRow();f=a.stepBackward();if(!f||f.type.indexOf("end-tag")!==-1)return;if(this.voidElements&&!this.voidElements[l]){var h=r.getTokenAt(o.row,o.column+1),u=r.getLine(c),p=this.$getIndent(u),d=p+r.getTabString();return h&&h.value==="-1}var r=e("../../lib/oop"),i=e("../../lib/lang"),s=e("../../range").Range,o=e("./fold_mode").FoldMode,u=e("../../token_iterator").TokenIterator,a=t.FoldMode=function(e,t){o.call(this),this.voidElements=e||{},this.optionalEndTags=r.mixin({},this.voidElements),t&&r.mixin(this.optionalEndTags,t)};r.inherits(a,o);var f=function(){this.tagName="",this.closing=!1,this.selfClosing=!1,this.start={row:0,column:0},this.end={row:0,column:0}};(function(){this.getFoldWidget=function(e,t,n){var r=this._getFirstTagInLine(e,n);return r?r.closing||!r.tagName&&r.selfClosing?t=="markbeginend"?"end":"":!r.tagName||r.selfClosing||this.voidElements.hasOwnProperty(r.tagName.toLowerCase())?"":this._findEndTagInLine(e,n,r.tagName,r.end.column)?"":"start":""},this._getFirstTagInLine=function(e,t){var n=e.getTokens(t),r=new f;for(var i=0;i";break}}return r}if(l(s,"tag-close"))return r.selfClosing=s.value=="/>",r;r.start.column+=s.value.length}return null},this._findEndTagInLine=function(e,t,n,r){var i=e.getTokens(t),s=0;for(var o=0;o",n.end.row=e.getCurrentTokenRow(),n.end.column=e.getCurrentTokenColumn()+t.value.length,e.stepForward(),n;while(t=e.stepForward());return null},this._readTagBackward=function(e){var t=e.getCurrentToken();if(!t)return null;var n=new f;do{if(l(t,"tag-open"))return n.closing=l(t,"end-tag-open"),n.start.row=e.getCurrentTokenRow(),n.start.column=e.getCurrentTokenColumn(),e.stepBackward(),n;l(t,"tag-name")?n.tagName=t.value:l(t,"tag-close")&&(n.selfClosing=t.value=="/>",n.end.row=e.getCurrentTokenRow(),n.end.column=e.getCurrentTokenColumn()+t.value.length)}while(t=e.stepBackward());return null},this._pop=function(e,t){while(e.length){var n=e[e.length-1];if(!t||n.tagName==t.tagName)return e.pop();if(this.optionalEndTags.hasOwnProperty(n.tagName)){e.pop();continue}return null}},this.getFoldWidgetRange=function(e,t,n){var r=this._getFirstTagInLine(e,n);if(!r)return null;var i=r.closing||r.selfClosing,o=[],a;if(!i){var f=new u(e,n,r.start.column),l={row:n,column:r.start.column+r.tagName.length+2};r.start.row==r.end.row&&(l.column=r.end.column);while(a=this._readTagForward(f)){if(a.selfClosing){if(!o.length)return a.start.column+=a.tagName.length+2,a.end.column-=2,s.fromPoints(a.start,a.end);continue}if(a.closing){this._pop(o,a);if(o.length==0)return s.fromPoints(l,a.start)}else o.push(a)}}else{var f=new u(e,n,r.end.column),c={row:n,column:r.start.column};while(a=this._readTagBackward(f)){if(a.selfClosing){if(!o.length)return a.start.column+=a.tagName.length+2,a.end.column-=2,s.fromPoints(a.start,a.end);continue}if(!a.closing){this._pop(o,a);if(o.length==0)return a.start.column+=a.tagName.length+2,a.start.row==a.end.row&&a.start.column"},this.createWorker=function(e){var t=new f(["ace"],"ace/mode/xml_worker","Worker");return t.attachToDocument(e.getDocument()),t.on("error",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/xml"}.call(l.prototype),t.Mode=l}) -------------------------------------------------------------------------------- /docs/04-migration.Rmd: -------------------------------------------------------------------------------- 1 | # Migration 2 | 3 | Usually, it is easier to start a new website than migrating\index{Site Migration} an old one to a new framework, but you may have to do it anyway because of the useful content on the old website that should not simply be discarded. A lazy solution is to leave the old website as is, start a new website with a new domain, and provide a link to the old website. This may be a hassle to your readers, and they may not be able to easily discover the gems that you created on your old website, so I recommend that you migrate your old posts and pages to the new website if possible. 4 | 5 | This process may be easy or hard, depending on how complicated your old website is. The bad news is that there is unlikely to be a universal or magical solution, but I have provided some helper functions in **blogdown** as well as a Shiny application to assist you, which may make it a little easier for you to migrate from Jekyll and WordPress sites. 6 | 7 | To give you an idea about the possible amount of work required, I can tell you that it took me a whole week (from the morning to midnight every day) to migrate several of my personal Jekyll-based websites to Hugo and **blogdown**. The complication in my case was not only Jekyll, but also the fact that I built several separate Jekyll websites (because I did not have a choice in Jekyll) and I wanted to unite them in the same repository. Now my two blogs (Chinese and English), the **knitr** [@R-knitr] package documentation, and the **animation** package [@R-animation] documentation are maintained in the same repository: https://github.com/rbind/yihui. I have about 1000 pages on this website, most of which are blog posts. It used to take me more than 30 seconds to preview my blog in Jekyll, and now it takes less than 2 seconds to build the site in Hugo. 8 | 9 | Another complicated example is the website of Rob J Hyndman (https://robjhyndman.com). He started his website in 1993 (12 years before me), and had accumulated a lot of content over the years. You can read the post https://support.rbind.io/2017/05/15/converting-robjhyndman-to-blogdown/ for the stories about how he migrated his WordPress website to **blogdown**. The key is that you probably need a long international flight when you want to migrate a complicated website. 10 | 11 | A simpler example is the Simply Statistics blog (https://simplystatistics.org). Originally it was built on Jekyll^[It was migrated from WordPress a few years ago. The WordPress site was actually migrated from an earlier Tumblr blog.] and the source was hosted in the GitHub repository https://github.com/simplystats/simplystats.github.io. I volunteered to help them move to **blogdown**, and it took me about four hours. My time was mostly spent on cleaning up the YAML metadata of posts and tweaking the Hugo theme. They had about 1000 posts, which sounds like a lot, but the number does not really matter, because I wrote an R script to process all posts automatically. The new repository is at https://github.com/rbind/simplystats. 12 | 13 | If you do not really have too many pages (e.g., under 20), I recommend that you cut and paste them to Markdown files, because it may actually take longer to write a script to process these pages. 14 | 15 | It is likely that some links will be broken after the migration because Hugo renders different links for your pages and posts. In that case, you may either fix the permanent links (e.g., by tweaking the slug of a post), or use 301 redirects (e.g., on Netlify). 16 | 17 | ## From Jekyll 18 | 19 | When converting a Jekyll\index{Jekyll} website to Hugo, the most challenging part is the theme. If you want to keep exactly the same theme, you will have to rewrite your Jekyll templates using Hugo's syntax (see Section \@ref(templates)). However, if you can find an existing theme in Hugo (https://themes.gohugo.io), things will be much easier, and you only need to move the content of your website to Hugo, which is relatively easy. Basically, you copy the Markdown pages and posts to the `content/` directory in Hugo and tweak these text files. 20 | 21 | Usually, posts in Jekyll are under the `_posts/` directory, and you can move them to `content/post/` (you are free to use other directories). Then you need to define a custom rule for permanent URLs in `config.toml` like (see Section \@ref(options)): 22 | 23 | ```js 24 | [permalinks] 25 | post = "/:year/:month/:day/:slug/" 26 | ``` 27 | 28 | This depends on the format of the URLs you used in Jekyll (see the `permalink` option in your `_config.yml`). 29 | 30 | If there are static assets like images, they can be moved to the `static/` directory in Hugo. 31 | 32 | Then you need to use your favorite tool with some string manipulation techniques to process all Markdown files. If you use R, you can list all Markdown files and process them one by one in a loop. Below is a sketch of the code: 33 | 34 | ```{r eval=FALSE, tidy=FALSE} 35 | files = list.files( 36 | 'content/', '[.](md|markdown)$', full.names = TRUE, 37 | recursive = TRUE 38 | ) 39 | for (f in files) { 40 | xfun::process_file(f, function(x) { 41 | # process x here and return the modified x 42 | x 43 | }) 44 | } 45 | ``` 46 | 47 | The `process_file()` function from the **xfun** package takes a filename and a processor function to manipulate the content of the file, and writes the modified text back to the file. 48 | 49 | To give you an idea of what a processor function may look like, I provided a few simple helper functions in **blogdown**, and below are two of them: 50 | 51 | ```{r comment=''} 52 | blogdown:::remove_extra_empty_lines 53 | blogdown:::process_bare_urls 54 | ``` 55 | 56 | The first function substitutes two or more empty lines with a single empty line. The second function replaces links of the form `[url](url)` with ``. There is nothing wrong with excessive empty lines or the syntax `[url](url)`, though. These helper functions may make your Markdown text a little cleaner. You can find all such helper functions at https://github.com/rstudio/blogdown/blob/master/R/clean.R. Note they are not exported from **blogdown**, so you need triple colons to access them. 57 | 58 | The YAML metadata of your posts may not be completely clean, especially when your Jekyll website was actually converted from an earlier WordPress website. The internal helper function `blogdown:::modify_yaml()` may help you clean up the metadata. For example, below is the YAML metadata of a blog post of Simply Statistics when it was built on Jekyll: 59 | 60 | ```yaml 61 | --- 62 | id: 4155 63 | title: Announcing the JHU Data Science Hackathon 2015 64 | date: 2015-07-28T13:31:04+00:00 65 | author: Roger Peng 66 | layout: post 67 | guid: http://simplystatistics.org/?p=4155 68 | permalink: /2015/07/28/announcing-the-jhu-data-science-hackathon-2015 69 | pe_theme_meta: 70 | - 'O:8:"stdClass":2:{s:7:"gallery";O:8:"stdClass":...}' 71 | al2fb_facebook_link_id: 72 | - 136171103105421_837886222933902 73 | al2fb_facebook_link_time: 74 | - 2015-07-28T17:31:11+00:00 75 | al2fb_facebook_link_picture: 76 | - post=http://simplystatistics.org/?al2fb_image=1 77 | dsq_thread_id: 78 | - 3980278933 79 | categories: 80 | - Uncategorized 81 | --- 82 | ``` 83 | 84 | You can discard the YAML fields that are not useful in Hugo. For example, you may only keep the fields `title`, `author`, `date`, `categories`, and `tags`, and discard other fields. Actually, you may also want to add a `slug` field that takes the base filename of the post (with the leading date removed). For example, when the post filename is `2015-07-28-announcing-the-jhu-data-science-hackathon-2015.md`, you may want to add `slug: announcing-the-jhu-data-science-hackathon-2015` to make sure the URL of the post on the new site remains the same. 85 | 86 | Here is the code to process the YAML metadata of all posts: 87 | 88 | ```{r eval=FALSE, tidy=FALSE} 89 | for (f in files) { 90 | blogdown:::modify_yaml(f, slug = function(old, yaml) { 91 | # YYYY-mm-dd-name.md -> name 92 | gsub('^\\d{4}-\\d{2}-\\d{2}-|[.](md|markdown)', '', f) 93 | }, categories = function(old, yaml) { 94 | # remove the Uncategorized category 95 | setdiff(old, 'Uncategorized') 96 | }, .keep_fields = c( 97 | 'title', 'author', 'date', 'categories', 'tags', 'slug' 98 | ), .keep_empty = FALSE) 99 | } 100 | ``` 101 | 102 | You can pass a file path to `modify_yaml()`, define new YAML values (or functions to return new values based on the old values), and decide which fields to preserve (`.keep_fields`). You may discard empty fields via `.keep_empty = FALSE`. The processed YAML metadata is below, which looks much cleaner: 103 | 104 | ```yaml 105 | --- 106 | title: Announcing the JHU Data Science Hackathon 2015 107 | author: Roger Peng 108 | date: '2015-07-28T13:31:04+00:00' 109 | slug: announcing-the-jhu-data-science-hackathon-2015 110 | --- 111 | ``` 112 | 113 | ## From WordPress 114 | 115 | From our experience, the best way to import WordPress\index{WordPress} blog posts to Hugo is to import them to Jekyll, and write an R script to clean up the YAML metadata of all pages if necessary, instead of using the migration tools listed on the [official guide,](https://gohugo.io/tools/) including the WordPress plugin `wordpress-to-hugo-exporter`. 116 | 117 | To our knowledge, the best tool to convert a WordPress website to Jekyll is the Python tool [Exitwp.](https://github.com/thomasf/exitwp) Its author has provided detailed instructions on how to use it. You have to know how to install Python libraries and execute Python scripts. If you do not, I have provided an online tool at https://github.com/yihui/travis-exitwp. You can upload your WordPress XML file there, and get a download link to a ZIP archive that contains your posts in Markdown. 118 | 119 | The biggest challenge in converting WordPress posts to Hugo is to clean up the post content in Markdown. Fortunately, I have done this for three different WordPress blogs,^[The RViews blog (https://rviews.rstudio.com), the RStudio blog (https://blog.rstudio.com), and Karl Broman's blog (http://kbroman.org). The RViews blog took me a few days. The RStudio blog took me one day. Karl Broman's blog took me an hour.] and I think I have managed to automate this process as much as possible. You may refer to the pull request I submitted to Karl Broman to convert his WordPress posts to Markdown (https://github.com/kbroman/oldblog_xml/pull/1), in which I provided both the R script and the Markdown files. I recommend that you go to the "Commits" tab and view all my GIT commits one by one to see the full process. 120 | 121 | The key is the R script https://github.com/yihui/oldblog_xml/blob/master/convert.R, which converts the WordPress XML file to Markdown posts and cleans them. Before you run this script on your XML file, you have to adjust a few parameters, such as the XML filename, your old WordPress site's URL, and your new blog's URL. 122 | 123 | Note that this script depends on the Exitwp tool. If you do not know how to run Exitwp, please use the online tool I mentioned before (travis-exitwp), and skip the R code that calls Exitwp. 124 | 125 | The Markdown posts should be fairly clean after the conversion, but there may be remaining HTML tags in your posts, such as `` and `
    `. You will need to manually clean them, if any exist. 126 | 127 | ## From other systems 128 | 129 | If you have a website built by other applications or systems, your best way to go may be to import your website to WordPress first, export it to Jekyll, and clean up the Markdown files. You can try to search for solutions like "how to import blogger.com to WordPress" or "how to import Tumblr to WordPress." 130 | 131 | If you are very familiar with web scraping techniques, you can also scrape the HTML pages of your website, and convert them to Markdown via Pandoc, e.g., 132 | 133 | ```{r eval=FALSE, tidy=FALSE} 134 | rmarkdown::pandoc_convert( 135 | 'foo.html', to = 'markdown', output = 'foo.md' 136 | ) 137 | ``` 138 | 139 | I have actually tried this way on a website, but was not satisfied, since I still had to heavily clean up the Markdown files. If your website is simpler, this approach may work better for you. 140 | --------------------------------------------------------------------------------