Richie Cotton Richie Cotton - 4 months ago 10
LaTeX Question

How to autogenerate LaTeX \index entries for functions with knitr

I'm trying to create an index for a long

Rnw
document. I'd like each function called in the code chunks to have an index entry of the form

\index{*functionname* function (*packagecontainingthatfunction*)}


Rather than manually inspecting each code chunk and adding these index entries, it would be nice if knitr autogenerated these for me.

How do I do this?

I guess I need to override the behaviour of
knitr::render_latex
, but it isn't obvious to me what needs to change.

A sample
Rnw
file to play with:

\documentclass{article}

\usepackage[]{imakeidx}
\makeindex

\begin{document}

<<>>=
y <- log(sqrt(1:10))
@

\printindex

\end{document}

Answer

Use a hook.

This function will walk recursively through an expression, and whenever it finds a function, it returns \index{functionname function (packagename)}.

create_index_entry_from_expressions <- function(exprs)
{
  lapply(
    as.list(exprs),
    function(li)
    {
      # Load packages of functions accessed by :: or :::
      if(is.call(li) && grepl("^:{2,3}$", deparse(li[[1]])))
      {
        library(deparse(li[[2]]), character.only = TRUE)
      }
      if(is.name(li))
      {
        name_of_obj <- deparse(li)
        obj <- mget(
          name_of_obj, 
          mode = "function", 
          ifnotfound = NA_character_, 
          inherits = TRUE
        )[[1]]
        location <- find(name_of_obj)
        if(!is.na(name_of_obj) && 
           is.function(obj) && 
           grepl("^[[:alpha:]]", name_of_obj) && 
           grepl("^package:", location))
        {
          return(
            paste0(
              "\\index{", 
              knitr:::escape_latex(name_of_obj), 
              " function (", 
              substring(location, 9), 
              ")}"
            )
          )
        }
        return(NULL)
      }
      if(!is.language(li)) return(NULL)
      create_index_entry_from_expressions(li)
    }
  )
}

This hook will add the index entries text into the .tex file, for any chunk that has indexit = TRUE as a chunk option. Define it early in the document.

knitr::knit_hooks$set(indexit = function(before, options, envir) {
  if (before) {
    exprs <- parse(text = options$code)
    result <- create_index_entry_from_expressions(exprs)
    unlist(result, use.names = FALSE)
  }
})
Comments