Как программно отредактировать заголовок YAML R markdown? - PullRequest
1 голос
/ 30 мая 2020

Возьмем, к примеру, этот файл Rmd - https://github.com/rstudio/learnr/blob/master/inst/tutorials/ex-setup-r/ex-setup-r.Rmd

Заголовок YAML имеет это -

output:
  learnr::tutorial:
    progressive: true
    allow_skip: true

Я хотел бы изменить это на -

output: 
  ioslides_presentation:
    widescreen: true

Есть ли способ сделать это редактирование программно, т.е. можно ли написать некоторую функцию, которая принимает файл Rmd в качестве входных данных, редактирует заголовок YAML и создает новый файл Rmd?

Спасибо!

1 Ответ

2 голосов
/ 30 мая 2020

Я думаю, что это может сделать быстрая функция.

change_yaml_matter <- function(input_file, ..., output_file) {
  input_lines <- readLines(input_file)
  delimiters <- grep("^---\\s*$", input_lines)
  if (!length(delimiters)) {
    stop("unable to find yaml delimiters")
  } else if (length(delimiters) == 1L) {
    if (delimiters[1] == 1L) {
      stop("cannot find second delimiter, first is on line 1")
    } else {
      # found just one set, assume it is *closing* the yaml matter;
      # fake a preceding line of delimiter
      delimiters <- c(0L, delimiters[1])
    }
  }
  delimiters <- delimiters[1:2]
  yaml_list <- yaml::yaml.load(input_lines[ (delimiters[1]+1):(delimiters[2]-1) ])

  dots <- list(...)
  yaml_list <- c(yaml_list[ setdiff(names(yaml_list), names(dots)) ], dots)

  output_lines <- c(
    if (delimiters[1] > 0) input_lines[1:(delimiters[1])],
    strsplit(yaml::as.yaml(yaml_list), "\n")[[1]],
    input_lines[ -(1:(delimiters[2]-1)) ]
  )

  if (missing(output_file)) {
    return(output_lines)
  } else {
    writeLines(output_lines, con = output_file)
    return(invisible(output_lines))
  }
}

Где ... - это все, что вы хотите, . Значение: если вы хотите заменить компонент output: в yaml, то вы задаете именованный список как output=list(...).

Если я использую документ rmarkdown, который я использовал в предыдущем ответе , затем без изменений , он будет выглядеть так:

readLines("~/StackOverflow/1883604/62095186.Rmd")
#  [1] "---"                                              
#  [2] "title: Hello"                                     
#  [3] "output: html_document"                            
#  [4] "params:"                                          
#  [5] "  intab: TRUE"                                    
#  [6] "---"                                              
#  [7] ""                                                 
#  [8] "# Headline 1"                                     
#  [9] ""                                                 
# [10] "## Headline 2 `r if (params$intab) \"{.tabset}\"`"
# [11] ""                                                 
# [12] "### Headline 3 in a tab"                          
# [13] ""                                                 
# [14] "### Headline 4 in a tab"                          
# [15] ""                                                 
# [16] "### Headline 5 in a tab"                          
# [17] ""                                                 
# [18] ""                                                 

И чтобы измените часть output, я добавлю вложенный именованный список как:

change_yaml_matter("~/StackOverflow/1883604/62095186.Rmd", 
                   output=list(ioslides_presentation=list(widescreen=TRUE)))
#  [1] "---"                                              
#  [2] "title: Hello"                                     
#  [3] "params:"                                          
#  [4] "  intab: yes"                                     
#  [5] "output:"                                          
#  [6] "  ioslides_presentation:"                         
#  [7] "    widescreen: yes"                              
#  [8] "---"                                              
#  [9] ""                                                 
# [10] "# Headline 1"                                     
# [11] ""                                                 
# [12] "## Headline 2 `r if (params$intab) \"{.tabset}\"`"
# [13] ""                                                 
# [14] "### Headline 3 in a tab"                          
# [15] ""                                                 
# [16] "### Headline 4 in a tab"                          
# [17] ""                                                 
# [18] "### Headline 5 in a tab"                          
# [19] ""                                                 
# [20] ""                                                 

Вы можете изменить практически любую часть материала yaml. (Единственное, что вы не можете изменить, я подозреваю, это если у вас есть параметры yaml с именем input_file или output_file. Если у вас действительно есть файлы Rmd с этими параметрами верхнего уровня yaml, вы можете легко переименовать именованные аргументы здесь должно быть что-то еще, например Mxyzptlk и что-то еще ... вы вряд ли увидите их в производстве.)

Примечания:

  • Это ничего не сохраняло в файл, вы должны сделать это сами. Добавьте output_file="path/to/new.RMd" к вашему вызову, и он запишет новый файл.
  • Когда вы включите output_file= в аргументы, если вы решите не улавливать возвращаемое значение, оно будет ничего не вернуть. Это связано с invisible в моем возвращении; если вы действительно хотите увидеть и сохранить, либо захватите переменную и посмотрите на нее, либо заключите вызов функции в скобки, как в (change_yaml_matter(...)).

Уловка для YAML заключается в том, чтобы знать, что yaml:: будет рассматривать каждый верхний уровень как именованный элемент списка, и его содержимое будет рекурсивно списками таким же образом. Например,

str(yaml::yaml.load("
---
top1:
  level2a:
    level3a: 123
    level3b: 456
  level2b: 789
top2: quux
---"))
# List of 2
#  $ top1:List of 2
#   ..$ level2a:List of 2
#   .. ..$ level3a: int 123
#   .. ..$ level3b: int 456
#   ..$ level2b: int 789
#  $ top2: chr "quux"

Чтобы назначить новые значения, просто укажите вложенные именованные списки.

...