R Markdownの
オリジナルフォーマットを
作ろう

Tokyo.R 85

Atusy

2020-5-23

Atusy

  • R Markdown関係のコミッタ
  • felp、ftExtra、minidownなどパッケージを作ってはTokyoRで紹介している
  • Pythonでデータ分析してる
  • blog.atusy.net
  • @Atsushi776

minidown on CRAN

mini_document

  • 軽量CSSフレームワーク採用
  • JavaScriptを最小限にしつつも高機能
    • floating_toc
    • code_folding
    • code_download (開発版)
    • など

https://minidown.atusy.net

今日のお題

YAMLフロントマターに書くアレの

オリジナル版を作れるようになろう

output: html_document # アレ

アレ is 関数

フォーマット作ろうよ

YAMLシンプルにしたいやん?

output: tokyor85down::rich_html_document

必要ある?

R Markdownの内部とテンプレート開発 by kazutan氏

によると

だいたいYAMLフロントマター指定すれば済むからフォーマット作らなくていいよ(意訳)

らしいが……

コピペ撲滅委員会「あるよ!」

output:
  html_document:
    toc: TRUE
    toc_float: TRUE
    number_sections: TRUE
    code_folding: show
    code_download: TRUE
  • コピペしたい?
  • どこ変更したっけ?
  • 勝手に弄られないと信じられる?

YAMLで済む範囲も
フォーマット化する価値あり

要件: パッケージを書ける

参考: 2019年版Rパッケージ開発の手引き by uri氏

フルスクラッチが怖いなら
atusy/tokyor85down
を弄るところから始めよう

インストールするには
remotes::install_github("atusy/tokyor85down)

Lv.1 ラッパー定義

html_documentとかは関数

ラッパー関数もoutputに指定可!

html_documentは関数だよ

YAML使わないなら↓な感じ。

library(rmarkdown)
fmt <- html_document(
  toc = TRUE,
  toc_float = TRUE,
  number_sections = TRUE,
  code_folding = "show",
  code_download = TRUE,
)
render("index.Rmd", output_format = fmt)

使い回す操作はパッケージ化しよう

output: tokyor85down::rich_html_document
で済むようになるぞ!!

実装はGitHubで

tokyor85down::rich_html_document
#> function(# 後から無効にする可能性もあるなら、
#>                                #html_documentに渡す引数は
#>                                # bodyに直接書かず、引数として書いておくと良い
#>                                toc = TRUE,
#>                                toc_float = TRUE,
#>                                number_sections = TRUE,
#>                                code_folding = "show",
#>                                code_download = TRUE,
#>                                ...) {
#>   rmarkdown::html_document(
#>     toc = toc,
#>     toc_float = toc_float,
#>     number_sections = number_sections,
#>     code_folding = code_folding,
#>     code_download = code_download,
#>     ...
#>   )
#> }
#> <bytecode: 0x556eeaa24600>
#> <environment: namespace:tokyor85down>

Lv.2 依存ファイル追加

基本編(絶対パスOK)

HTMLでself_contained = FALSEを使わない限りは、基本編で十分なはず。

Wordの見出しに番号が欲しい!

ではYAMLでreference_docxを指定しましょう。

output:
  word_document:
    reference_docx: reference.docx

コピペ撲滅委員会
「プロジェクトごとにファイルコピペとか許せん」

依存ファイルをパッケージに追加

必要なファイルを
パッケージのinstディレクトリ以下の
好きなところに保存しておく

例えばinst/docx/reference.docx

Wordのテンプレを差し替え

実装はGitHubで

# パッケージからテンプレを探す関数
tokyor85down::spec_reference_docx
# reference_docx引数の既定値を
# spec_reference_docx()
# に変更したword_document
tokyor85down::styled_word_document

Lv.3 依存ファイル追加

応用編(絶対パスNG)

たとえば
html_document関数にオリジナルの
cssを指定したい

self_contained: FALSEな時は
プロジェクトディレクトリにCSSを
自動でコピーして欲しい

CSS引数を弄るとBAD

spec_css <- function () {
  system.file(
    "inst", "resources", "style.css",
    package = "tokyor85down")
}
styled_html_document <- 
  function(css = spec_css(), ...) {
  html_document(css = css, ...)
}

CSS追加との相性がBAD

styled_html_document(css = "new.css")

パッケージ提供のCSSを無視してしまう

HTMLの携帯性もBAD

styled_html_document(
  self_contained = FALSE
)

パッケージディレクトリ下にある
CSSを絶対パスで参照

extra_dependencies引数を使おう

依存したいCSSとかJavaScriptを
self_contained

  • TRUEなら
    • HTMLファイルに丸ごと取り込む
  • FALSEなら
    • lib_dir引数で指定した場所にコピーし
    • HTMLファイルから相対パスで参照する

実装はGitHubで

Lv.4 output_format関数へ

もっと自在に前処理とか後処理したくなったら

rmarkdown::output_format関数を使おう

詳しくは↓

R Markdownの内部とテンプレート開発 by kazutan氏

base_formatを改造できる

チャンクを実行しても一切出力に反映しない
ジョークフォーマットに魔改造できる

output_format(
  knitr = knitr_options(
    opts_chunk = list(include = FALSE)
  ),
  base_format = html_document()
)

返り値はリスト

fmt <- rmarkdown::output_format(NULL, NULL)
str(fmt)
#> List of 11
#>  $ knitr                  : NULL
#>  $ pandoc                 : NULL
#>  $ keep_md                : logi FALSE
#>  $ clean_supporting       : logi TRUE
#>  $ df_print               : NULL
#>  $ pre_knit               : NULL
#>  $ post_knit              : NULL
#>  $ pre_processor          : NULL
#>  $ intermediates_generator: NULL
#>  $ post_processor         : NULL
#>  $ on_exit                : NULL
#>  - attr(*, "class")= chr "rmarkdown_output_format"

要素の名前は引数由来

ただしbase_formatを除く

nm_arg <- names(formals(output_format))
nm_fmt <- names(fmt)

setdiff(nm_arg, nm_fmt)
#> [1] "base_format"
setdiff(nm_fmt, nm_arg)
#> character(0)

html_documentとの比較

html_fmt <- html_document()

identical(nm_fmt, names(html_fmt))
#> [1] TRUE

str(html_fmt)
#> List of 11
#>  $ knitr                  :List of 5
#>   ..$ opts_knit    : NULL
#>   ..$ opts_chunk   :List of 5
#>   .. ..$ dev       : chr "png"
#>   .. ..$ dpi       : num 96
#>   .. ..$ fig.width : num 7
#>   .. ..$ fig.height: num 5
#>   .. ..$ fig.retina: num 2
#>   ..$ knit_hooks   : NULL
#>   ..$ opts_hooks   : NULL
#>   ..$ opts_template: NULL
#>  $ pandoc                 :List of 6
#>   ..$ to          : chr "html"
#>   ..$ from        : chr "markdown+autolink_bare_uris+tex_math_single_backslash+smart"
#>   ..$ args        : chr [1:10] "--email-obfuscation" "none" "--self-contained" "--standalone" ...
#>   ..$ keep_tex    : logi FALSE
#>   ..$ latex_engine: chr "pdflatex"
#>   ..$ ext         : NULL
#>  $ keep_md                : logi FALSE
#>  $ clean_supporting       : logi TRUE
#>  $ df_print               : chr "default"
#>  $ pre_knit               :function (...)  
#>   ..- attr(*, "srcref")= 'srcref' int [1:8] 115 5 117 5 5 5 4614 4616
#>   .. ..- attr(*, "srcfile")=Classes 'srcfilealias', 'srcfile' <environment: 0x556ee8977758> 
#>  $ post_knit              :function (...)  
#>   ..- attr(*, "srcref")= 'srcref' int [1:8] 115 5 117 5 5 5 4614 4616
#>   .. ..- attr(*, "srcfile")=Classes 'srcfilealias', 'srcfile' <environment: 0x556ee8977758> 
#>  $ pre_processor          :function (...)  
#>   ..- attr(*, "srcref")= 'srcref' int [1:8] 115 5 117 5 5 5 4614 4616
#>   .. ..- attr(*, "srcfile")=Classes 'srcfilealias', 'srcfile' <environment: 0x556ee8977758> 
#>  $ intermediates_generator:function (original_input, intermediates_dir)  
#>   ..- attr(*, "srcref")= 'srcref' int [1:8] 118 30 122 3 30 3 1793 1797
#>   .. ..- attr(*, "srcfile")=Classes 'srcfilealias', 'srcfile' <environment: 0x556ee97f1e28> 
#>  $ post_processor         :function (metadata, input_file, output_file, clean, verbose)  
#>   ..- attr(*, "srcref")= 'srcref' int [1:8] 124 21 172 3 21 3 1799 1847
#>   .. ..- attr(*, "srcfile")=Classes 'srcfilealias', 'srcfile' <environment: 0x556ee97f1e28> 
#>  $ on_exit                :function ()  
#>   ..- attr(*, "srcref")= 'srcref' int [1:8] 182 3 185 3 3 3 4681 4684
#>   .. ..- attr(*, "srcfile")=Classes 'srcfilealias', 'srcfile' <environment: 0x556ee8977758> 
#>  - attr(*, "class")= chr "rmarkdown_output_format"

なぜoutput_formatで改造するのか?

  • html_document()がリストなら直接改造もできる

  • しかしoutput_formatは、改造した部分を良い感じに base_formatと結合してくれる

  • ただし、keep_mdclean_supportedself_contained相当)を除く

    • 新しいフォーマットのkeep_md引数とself_contained引数は、 base_formatではなく、output_formatに食わせること。

on_exit処理の結合例

# on_exitには最終処理を記述すると
output_format(
  on_exit = function(...) message("Almost done"),
  base_format = html_document()
)
# 2つのon_exitを結合してくれる
function(...) c(
    html_document()$on_exit(...),
    function(...) message("Almost done")
  )

結合方法の詳細は

rmarkdown:::merge_output_formatsの実装を見てネ。

Lv.5 テンプレ.Rmd作り

File -> New File -> R Markdown...
から選べるテンプレを作るには

GOTO

R Markdownの内部とテンプレート開発 by kazutan氏

Lv.0 self_contained例外処理

参考

Lv.? Lua Filter

使い方

pandoc_args引数に指定

output:
  html_document:
    pandoc_args:
      - "--lua-filter"
      - "filters.lua"

Enjoy & Support Me!

  • Rにこれからもどんどん貢献したい
    • プロジェクトにもコミュニティにも
    • ただし趣味として
  • TwitterとかGitHubで褒めてください
  • スポンサーもして頂けると非常にありがたいです
    • モチベーション向上
    • 開発環境や移動のための予算確保

https://github.com/sponsors/atusy