Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

opts_current$set should error #1798

Closed
3 tasks done
AshesITR opened this issue Jan 19, 2020 · 21 comments
Closed
3 tasks done

opts_current$set should error #1798

AshesITR opened this issue Jan 19, 2020 · 21 comments
Assignees
Labels
feature Feature requests

Comments

@AshesITR
Copy link

As per #841 (wontfix), you're not going to support changing the current chunk options.
However, opts_current$set allows modifying the options and doesn't complain about it either.
It even succeeds in changing an option, but at least for results='asis' this leaves opts_current in an inconsistent state because the updated option is not honored.

Note how knitting the MWE, the first section reports results = markup and then results = asis despite the cat(...) being captured and commented instead.

Since programmatically updating the current chunk options will not be supported, I suggest to at least stop() when code is trying to call opts_current$set().

MWE

---
title: "Asis output test"
output: html_document
---

```{r functions}
# This is the source code chunk we want to see
the_source <- c(
  "```r",
  "a <- 'It worked. Yay!'",
  "",
  "cat(a, \"\n\", sep = \"\")",
  "# Comments in the code",
  "```"
)
try_print <- function() {
  cat("opts_current-results=", 
      knitr::opts_current$get("results"), "  \n", sep = "")
  knitr::opts_current$set(results = "asis")
  cat("opts_current-results=", 
      knitr::opts_current$get("results"), "  \n", sep = "")
  cat(the_source, sep = "\n")
  42
}
```

## Default chunk options

```{r}
try_print()
```

## Result=asis chunk option

```{r results='asis'}
try_print()
```

By filing an issue to this repo, I promise that

  • I have fully read the issue guide at https://yihui.name/issue/.
  • I have provided the necessary information about my issue.
    • If I'm asking a question, I have already asked it on Stack Overflow or RStudio Community, waited for at least 24 hours, and included a link to my question there.
    • If I'm filing a bug report, I have included a minimal, self-contained, and reproducible example, and have also included xfun::session_info('knitr'). I have upgraded all my packages to their latest versions (e.g., R, RStudio, and R packages), and also tried the development version: remotes::install_github('yihui/knitr').
    • If I have posted the same issue elsewhere, I have also mentioned it in this issue.
  • I have learned the Github Markdown syntax, and formatted my issue correctly.

I understand that my issue may be closed if I don't fulfill my promises.

@AshesITR
Copy link
Author

Here's the relevant output of the MWE:

grafik

@cderv cderv added the feature Feature requests label Jan 29, 2021
@yihui yihui self-assigned this Aug 30, 2023
yihui added a commit that referenced this issue Aug 30, 2023
@yihui yihui closed this as completed in 7cd115a Aug 30, 2023
@yihui
Copy link
Owner

yihui commented Aug 30, 2023

opts_current is locked now. Users can no longer run its $set() method within code chunks. Thanks!

@gadenbuie
Copy link
Contributor

@yihui what's the best way around this if I have a legitimate use of opts_current$set() that just didn't happen to have tests on CRAN yet?

@gadenbuie
Copy link
Contributor

@yihui I figured out a way around this that will work. But I do want to point out that the documentation for opts_current has included the following for a long time

opts_current should be treated as read-only and you are supposed to only query its values via opts_current$get(). Technically you could also call opts_current$set() to change the values, but you are not recommended to do so unless you understand the consequences.

Relative to this advice, locking the binding is quite an aggressive change to make in a minor version release.

@yihui
Copy link
Owner

yihui commented Sep 11, 2023

@gadenbuie Please feel free to unlock it by yourself with knitr::opts_current$lock(FALSE). Normally I hate forcing a decision onto users, so you can rest assured that the door is rarely tightly shut.

I'll update the documentation accordingly. Thanks for the reminder!

BTW, I'm curious about your use of opts_current$set(). Could you tell me more about it?

@gadenbuie
Copy link
Contributor

Thanks @yihui! I appreciate your open door philosophy 😄 I read the comments in the "different method" commit and was worried that $lock(FALSE) was something that would go away eventually.

My use of opts_current$set() is actually to work around the limitation that opts_current$get() leaks chunk options from full R chunks to subsequent inline chunks (#1988).

```{r full-chunk, my_special_option = "hello", include = FALSE}
# some full chunk
```

`r knitr::opts_current$get("my_special_option")`
`r knitr::opts_current$get("label")`

Which renders to the following since opts_current$get() finds variables set in the previous chunk.

hello full-chunk

I found a clever way around this by using a knit_hook that runs before and after every chunk to set a flag that's only TRUE while inside a full chunk. For it to work, I needed to use opts_current$set() that sets that flag to !before, which later ensures that it's FALSE when opts_current$get() is called inside an inline chunk.

I'm now using a package-scoped global variable to hold the flag, which is fine. But I preferred opts_current$set(), since it's tightly scoped to knitr chunks and I don't have to worry about the knit lifecycle (since the package variable outlives the knit process [again, in this case it's okay but feels slightly shaky]).

@yihui
Copy link
Owner

yihui commented Sep 12, 2023

Thanks for explaining the use case! That sounds reasonable to me.

Now I feel this change is indeed too aggressive. Let me think more about it and decide whether this should be a warning (with a hint in the message on how to unlock it) instead of an error. Sorry about the hassle!

@atusy
Copy link
Collaborator

atusy commented Sep 13, 2023

I would recommend using knit_code$get() rather than opts_curret$get for the usecase of @gadenbuie .

It allows getting from a specific label or listing in the evaluated order.

``` {r x, echo=TRUE}
print(1)
```

## get chunk options of the "x" chunk

```{r, echo=TRUE}
attr(knitr::knit_code$get("x"), "chunk_opts")
```

## get latest chunk (itself)

```{r}
tail(knitr::knit_code$get(), 1L)
```

@gadenbuie
Copy link
Contributor

Thanks @atusy but that's not my use case. To clarify, in my example above, the chunk option my_special_option and the chunk label -- which is essentially a chunk-scoped option -- should not be set outside the chunk in which they are defined unless they're also set globally. In this case, neither are globally scoped, so my position is that opts_current$get("my_special_option") shouldn't return "hello" outside of the full chunk where it was defined. knit_code$get() doesn't help in this case, since its focus is on block chunks only.

yihui added a commit to yihui/yihui.org that referenced this issue Sep 13, 2023
@cderv
Copy link
Collaborator

cderv commented Sep 13, 2023

I believe the underlying issue is also important to fix (adapt)

as I understand opts_current$set is used to fix it.

opts$current is not something that would be used inline usually so I don't think it was thought about when introduced. I guess opts$current should return only global options and not options from the previous chunk also.

@atusy
Copy link
Collaborator

atusy commented Sep 13, 2023

opts$current should return only global options

I agree.

Also, inline chunk may possibly gain chunk options in the future if we implement new syntax like `f()`{r, cache=TRUE}` discussed in #2026 (comment) .
Then, opts_current$get() certainly behave differently.

@cderv
Copy link
Collaborator

cderv commented Sep 13, 2023

FWIW @yihui it seems one of the usage is to be able to tweak some chunk options from within another hook
https://github.com/wjschne/apaquarto/pull/17/files/a156dd63a7cf7ed27c07d3e6435ae2e092bed97a#diff-4f7262d8ecef665a4aecd5bd3e1767a34b6085bf40e2f8aa466edb19c9d693e4

Is there another design pattern to be able to do such modification of options from a hook that takes options as argument ?

@yihui
Copy link
Owner

yihui commented Sep 21, 2023

Is there another design pattern to be able to do such modification of options from a hook that takes options as argument ?

@cderv Normally hooks shouldn't need the opts_current object at all. Current chunk options have already been passed to hooks via the options argument. I don't know why flextable package needs to use opts_current, but it seems to use the $get() method only. I don't know why the apaquarto project needs to set those two options, either. It will be good to know what exactly the problem they are trying to solve. Without understanding more, I feel a better solution for them is probably an option hook like:

knitr::opts_hooks$set(fig.env = function(options) {
  if (using_some_flextable_feature()) {
    # I wonder if they meant `results` instead of `output` here
    options$output = 'asis'; options$fig.env = NULL
  }
  options
})

opts_current should return only global options

The PR #2292 will do it.

@cderv
Copy link
Collaborator

cderv commented Sep 22, 2023

I wonder if they meant results instead of output here

I think they meant output because it is a quarto options.

It seems they are trying to fix something related to Quarto rendering and output.

Current chunk options have already been passed to hooks via the options argument.

I think what this all means is that: If you want to tweak options, then you need to modify options hooks.
No way to modify options content from another type of hooks (unless using <<- trick or opts_current unlocking - but not adviced)

@howardbaek
Copy link

Is the fig.alt chunk option not supported in opts_current$set()?

I've tried the following:

knitr::opts_current$lock(FALSE)

if (is.null(knitr::opts_current$get("fig.alt"))) {
  knitr::opts_current$set(fig.alt = "hello world")
}

plot(pressure)

and don't see the alt.

@cderv
Copy link
Collaborator

cderv commented Sep 28, 2023

@howardbaek setting option using opts_current is not really advise. This issue was about throwing an error when this was done, and it is meant for read only purposes only. We since added a locking / unlocking mechanism to help in some rare situation.
So as it was design for read only purposes, it is highly possible that fig.alt is not processed after you try to modify opts_current.

I believe your usecase is jhudsl/ottrpal#119 - maybe we can help with that and understand if there.

Example of how to modify options with options hook:

---
title: test
output: html_document
---

```{r}
knitr::opts_hooks$set(engine = function(options) {
  # do nothing for non R chunk
  if (tolower(options$engine) != "r") return(options)
  # if not fig.alt, then use a default one
  if(is.null(options$fig.alt)) {
    options$fig.alt = "individual slide notes"
  }
  options
})
```

```{r}
knitr::opts_current$get("fig.alt")
```

image

@yihui regarding jhudsl/ottrpal#119 use case, maybe you have insights and advices on how to modify options from within a function run in a chunk ? Question has also been asked in Communityso we could maybe follow up there too. (https://community.rstudio.com/t/setting-fig-alt-using-knitr-knit-hooks/174159)

@yihui
Copy link
Owner

yihui commented Sep 28, 2023

There is no way to modify chunk options from within the current code chunk. Currently, the only way to modify chunk options is via option hooks (like @cderv's example above) which occurs before a chunk is evaluated. When a chunk is being evaluated or after it is evaluated, it's no longer possible to modify its chunk options.

Calling opts_current$set() won't set chunk options.

@howardbaek
Copy link

howardbaek commented Sep 28, 2023

@cderv Thanks for the quick response. Your code sheds some light, but brings up followup questions.

I did try options hooks, but this didn't work:

knitr::opts_hooks$set(fig.alt = function(options) {
  # if not fig.alt, then use a default one
  if(is.null(options$fig.alt)) {
    options$fig.alt = "individual slide notes"
  }
  options
})

I don't fully understand how to use opts_hooks$set(). For my learning, why do we set engine as the function name inside opts_hooks$set(). This might be a related question, but what is the purpose of this line? if (tolower(options$engine) != "r") return(options)

Also, regarding jhudsl/ottrpal#119, I inserted your code inside include_slide()

include_slide <- function(url,
                          output_dir = knitr::opts_chunk$get("fig.path"),
                          overwrite = TRUE, ...) {
 # set fig.alt
  knitr::opts_hooks$set(engine = function(options) {
    # do nothing for non R chunk
    if (tolower(options$engine) != "r") return(options)
    # if not fig.alt, then use a default one
    if(is.null(options$fig.alt)) {
      options$fig.alt = "individual slide notes"
    }
    options
  })

  outfile <- gs_png_download(url, output_dir, overwrite = overwrite)
  knitr::include_graphics(outfile, ...)
}

and experimented inside a Rmd:

---
title: "test"
output: html_document
---

```{r}
ottrpal::include_slide("https://docs.google.com/presentation/d/1YmwKdIy9BeQ3EShgZhvtb3MgR8P6iDX4DfFD65W_gdQ/edit#slide=id.gcc4fbee202_0_141")

knitr::opts_current$get("fig.alt")
```

But the last line prints NULL, so don't think its working inside a function?

BTW, how did you insert Rmd code chunks into this issue without having to put a \ before the ```?

@cderv
Copy link
Collaborator

cderv commented Sep 29, 2023

I did try options hooks, but this didn't work:

You set an option hook on fig.alt directly. As we document in our doc (https://bookdown.org/yihui/rmarkdown-cookbook/option-hooks.html)

An option hook is a function associated with the option and to be executed when a corresponding chunk option is not NULL.

So you can't set a default fig.alt is there is no fig.alt initially (fig.alt = NULL). You need to set the option hook on another value.

For my learning, why do we set engine as the function name inside opts_hooks$set(). This might be a related question, but what is the purpose of this line? if (tolower(options$engine) != "r") return(options)

This is why I gave you the example with setting the option hook on the engine option, because it will always be non-NULL for a chunk. I added a test on engine R to give an example on how to restrict the behavior to R engine. (engine could be python or bash, or anything and you may want to only target R).

You could set the option hook to anything that has meaning for your issue. For example you could want to set a default fig.alt for figures, and assumes fig.cap will always be provided in this case, and you don't want fig.alt = fig.cap. You will set an option hook on fig.cap to set a default value for fig.alt, before knitr does set fig.alt = fig.cap by default when fig.alt is NULL.

I inserted your code inside include_slide() (...) But the last line prints NULL, so don't think its working inside a function?

You need to run the hook function once before it can apply on a chunk. As you put it in your function, called the function and in the same chunk test that the fig.alt is modified this won't work. However, for other following chunk, you should see the difference. But here you will setup the hook each time your function in run and you want it to apply on the same chunk, so I don't this will work like this.

Setting up a option hook will apply to the whole documents, and all chunks. Usually best way to set them up are:

  • Manually by a user in a setup chunk, so that it applies on all other document - but you probably don't want that
  • By providing a ottrpal::setup_rmd() (or similar) that a user should run in a setup chunk when using your tool.
  • by setting up the hook when your Package is loaded in a knitr context for example. A bit advanced but should work.
    • We're talking about .onLoad() or rlang::on_load()
    • So when a user to library(ottrpal), you would check knitr context and run the hooks setup. this should set up the hooks for all usage
    • You can probably find example of this on Github Search.
  • By creating a custom format ottrpal::html_document() that just modifies the default one by adding some knit_hooks for options

Hope it helps

BTW, how did you insert Rmd code chunks into this issue without having to put a \ before the ```?

Just follow the issue guide: https://yihui.org/issue/#please-format-your-issue-correctly

@howardbaek
Copy link

Yes, this helps a lot. What I'm trying to implement is not possible in knitr. I'll have to figure out a workaround fix.

clrpackages pushed a commit to clearlinux-pkgs/R-knitr that referenced this issue Oct 30, 2023
Christophe Dervieux (4):
      escape `fig.alt` for HTML output (#2291)
      close #2295: use a warning instead of an error for `opts_current` locking (#2296)
      Improve convert_chunk_header() for YAML options (#2300)
      Probably need to use the higher dependency gridSVG that requires XML not available on R 3.6.0

Pedro Faria (1):
      close #2226: add an error handler to improve YAML error message (#2294)

Yihui Xie (12):
      start the next version
      give users the key to unlock opts_current in the error message (yihui/knitr#1798)
      simplify this clumsy decade-old JS code
      the new version of papaja is on CRAN now: crsh/papaja#566
      fix #2288: preserve the minus sign when formatting negative numbers -10^n
      revise c1c9a5766a632fcb535fc432c7be401cba965c22 and ca7172752b6a3b4a5d2d9492e21844adad20198d: just don't use simple progress bars in RStudio, no matter where they are
      lingglosses v0.0.7 is on CRAN
      fix rstudio/rmarkdown#2518: when fig.num is a multiple of fig.ncol, add \newline to the last subfigure
      restore `opts_current` after each code chunk, and also after `knit()` exits (#2292)
      fix #2302: wrap figure output in raw latex blocks for Markdown output (#2303)
      revert 4307aedad27ec04defa35bf21d578feecd10665e and fix #2302 by escaping % instead
      CRAN release v1.45
Copy link

github-actions bot commented Apr 3, 2024

This old thread has been automatically locked. If you think you have found something related to this, please open a new issue by following the issue guide (https://yihui.org/issue/), and link to this old issue if necessary.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 3, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feature Feature requests
Projects
None yet
Development

No branches or pull requests

6 participants