Skip to content

Commit

Permalink
First pass at Better Bib(La)TeX integration! :D
Browse files Browse the repository at this point in the history
  • Loading branch information
crsh committed Aug 31, 2016
1 parent 6b29ad6 commit 120f7fd
Show file tree
Hide file tree
Showing 17 changed files with 382 additions and 90 deletions.
1 change: 1 addition & 0 deletions .Rbuildignore
Expand Up @@ -3,6 +3,7 @@

^inst/images/*
^README\.Rmd$
^README_files$
^\.travis\.yml$

^cran-comments\.md$
5 changes: 3 additions & 2 deletions DESCRIPTION
Expand Up @@ -3,7 +3,7 @@ Type: Package
Title: RStudio Add-in to Insert Markdown Citations
Description: Functions and an RStudio add-in that search a BibTeX-file to create and
insert formatted Markdown citations into the current document.
Version: 0.1.0
Version: 0.1.0.9026
Date: 2016-8-03
Authors@R: person("Frederik", "Aust", email = "frederik.aust@uni-koeln.de", role = c("aut", "cre"))
URL: https://github.com/crsh/citr
Expand All @@ -17,7 +17,8 @@ Imports:
miniUI (>= 0.1.1),
shiny (>= 0.13.2),
assertthat (>= 0.1),
methods (>= 3.0.0)
methods (>= 3.0.0),
curl (>= 1.1)
Suggests:
testthat (>= 1.0.2)
License: MIT + file LICENSE
Expand Down
181 changes: 143 additions & 38 deletions R/insert_citation.R
@@ -1,10 +1,12 @@
#' Invoke RStudio add-in to insert Markdown citations
#'
#' @param bib_file Character. Path to Bib(La)TeX-file. See details.
#' @inheritParams query_bib
#'
#' @details The path to the Bib(La)TeX-file can be set in the global options and is set to
#' \code{./references.bib} when the package is loaded. Once the path is changed in the
#' RStudio addin, the global option is updated.
#' RStudio addin, the global option is updated. If \code{use_betterbiblatex = TRUE} references
#' are imported from Zotero rather than from the Bib(La)TeX-file. The Bib(La)TeX-file
#' is then updated to include the inserted reference.
#'
#' If \code{insert_citation} is called while the focus is on a R Markdown document,
#' which includes a YAML front matter with paths to one or more bibliography files,
Expand All @@ -27,8 +29,11 @@
#' @import assertthat
#' @export

insert_citation <- function(bib_file = getOption("citr.bibliography_path")) {
insert_citation <- function(bib_file = getOption("citr.bibliography_path"), use_betterbiblatex = getOption("citr.use_betterbiblatex")) {
assert_that(is.character(bib_file))
assert_that(is.flag(use_betterbiblatex))

betterbiblatex <- betterbiblatex_available()

# Get bibliography files from YAML front matter if available
## Let's hope this doesn't cause too much trouble; this is a lot more sofisticated in rmarkdown, but the functions are not exported.
Expand All @@ -41,7 +46,7 @@ insert_citation <- function(bib_file = getOption("citr.bibliography_path")) {
(yaml_delimiters[2] - yaml_delimiters[1] > 1) &&
grepl("^---\\s*$", context$contents[yaml_delimiters[1]])) {
if(context$path == "") {
message("\nUnsaved R Markdown document: Cannot locate .bib-file(s); falling back to manual path specification.\n")
message("\nUnsaved R Markdown document: Cannot locate Bib(La)TeX file(s); falling back to manual path specification.\n")
} else {
yaml_params <- yaml::yaml.load(paste(context$contents[(yaml_delimiters[1] + 1):(yaml_delimiters[2] - 1)], collapse = "\n"))

Expand All @@ -50,9 +55,11 @@ insert_citation <- function(bib_file = getOption("citr.bibliography_path")) {
relative_paths <- !grepl("^\\/|~", yaml_bib_file)
absolute_yaml_bib_file <- yaml_bib_file
absolute_yaml_bib_file[relative_paths] <- paste(dirname(context$path), yaml_bib_file[relative_paths], sep = "/")
yaml_choices <- absolute_yaml_bib_file
names(yaml_choices) <- yaml_bib_file

# Reload if new bibliography paths are used
if(!isTRUE(all.equal(absolute_yaml_bib_file, getOption("citr.bibliography_path")))) {
if(!all(absolute_yaml_bib_file == getOption("citr.bibliography_path")) & (!betterbiblatex | !use_betterbiblatex)) {
options(citr.bibliography_path = absolute_yaml_bib_file)
options(citr.bibliography_cache = NULL)
}
Expand All @@ -64,7 +71,7 @@ insert_citation <- function(bib_file = getOption("citr.bibliography_path")) {
stableColumnLayout(
selectizeInput(
"selected_key"
, choices = c(`.bib-file not found` = "")
, choices = c(`No references found` = "")
, label = ""
, width = 700
, multiple = TRUE
Expand All @@ -79,35 +86,90 @@ insert_citation <- function(bib_file = getOption("citr.bibliography_path")) {
, miniTitleBarCancelButton()
)
),
br(),
hr(),
uiOutput("bib_file"),
uiOutput("zotero_status")
)
)

server <- function(input, output, session) {

# shinyFiles::shinyFileChoose(input, "files", root = c(`Working directory`= ".", Home = "~/"), filetypes = "bib", session = session)

# input$upload$datapath

reactive_variables <- reactiveValues(reload_bib = "init", use_betterbiblatex = use_betterbiblatex) # Set initial value

# Zotero use
observeEvent(input$disconnect_zotero, {
options(citr.use_betterbiblatex = FALSE)
reactive_variables$use_betterbiblatex <- FALSE

## Discard cache
options(citr.bibliography_cache = NULL)
reactive_variables$reload_bib <- paste0(sample(letters, 100, replace = TRUE), collapse = "") # Do stuff to trigger reload_bib reactive
})

observeEvent(input$connect_zotero, {
options(citr.use_betterbiblatex = TRUE)
reactive_variables$use_betterbiblatex <- TRUE

## Discard cache
options(citr.bibliography_cache = NULL)
reactive_variables$reload_bib <- paste0(sample(letters, 100, replace = TRUE), collapse = "") # Do stuff to trigger reload_bib reactive
})

output$zotero_status <- renderUI({
if(betterbiblatex) {
if(reactive_variables$use_betterbiblatex) {
helpText(
"Connected to Zotero."
, actionLink("discard_cache", "Reload database")
, "|"
, actionLink("disconnect_zotero", "Disconnect")
)
} else {
helpText(
"Not connected to Zotero."
, actionLink("connect_zotero", "Connect")
)
}
}
})

output$bib_file <- renderUI({
if(!yaml_found || is.null(yaml_bib_file)) {
div(
# shinyFiles::shinyFilesButton("files", label = "Select .bib-file(s)", title = "Please select one or more .bib-file", multiple = TRUE), # Only prespecified directories allowed
# fileInput("upload", label = "Upload", multiple = TRUE, accept = c("application/x-bibtex", "text/plain", ".bib", ".bibtex")), # Wouldn't know how to cache uploaded file
textInput("bib_file", "Path to .bib-file:", value = bib_file, width = 700),
textInput(
ifelse(betterbiblatex && reactive_variables$use_betterbiblatex, "update_bib", "read_bib")
, ifelse(betterbiblatex && reactive_variables$use_betterbiblatex, "Bib(La)Tex file to update", "Bib(La)Tex file to read")
, value = bib_file
, width = 700
),
helpText(
"YAML front matter missing or no bibliography file(s) specified."
, actionLink("discard_cache", "Reload bibliography")
, if(!betterbiblatex || !reactive_variables$use_betterbiblatex) actionLink("discard_cache", "Reload file")
)
)
} else {
helpText(
"Bibliography file(s) found in YAML front matter:"
, code(paste(yaml_bib_file, collapse = ", "))
, actionLink("discard_cache", "Reload")
)
if(betterbiblatex && reactive_variables$use_betterbiblatex) {
div(
selectInput("update_bib", "Bib(La)Tex file to update", choices = yaml_choices),
helpText("Bibliography file(s) found in YAML front matter.")
)
} else {
helpText(
"Bibliography file(s) found in YAML front matter:"
, code(paste(yaml_bib_file, collapse = ", "))
, actionLink("discard_cache", "Reload file(s)")
)
}
}
)
)

server <- function(input, output, session) {
})

# shinyFiles::shinyFileChoose(input, "files", root = c(`Working directory`= ".", Home = "~/"), filetypes = "bib", session = session)

# input$upload$datapath

# Discard cache reactive
reactive_variables <- reactiveValues(reload_bib = "init") # Set initial value
observeEvent(input$discard_cache, {
options(citr.bibliography_cache = NULL)
reactive_variables$reload_bib <- paste0(sample(letters, 100, replace = TRUE), collapse = "") # Do stuff to trigger reload_bib reactive
Expand All @@ -117,7 +179,10 @@ insert_citation <- function(bib_file = getOption("citr.bibliography_path")) {
# Load bibliography
bibliography <- reactive({
trigger <- reload_bib() # Triggers reactive when event link is clicked
if(!is.null(input$bib_file) && !isTRUE(all.equal(input$bib_file, getOption("citr.bibliography_path")))) {
if(
(!is.null(input$read_bib) && !(all(input$read_bib == getOption("citr.bibliography_path")))) ||
!reactive_variables$use_betterbiblatex #&& !is.null(input$update_bib) && !(all(input$update_bib == getOption("citr.bibliography_path"))))
) {
options(citr.bibliography_cache = NULL)
}

Expand All @@ -127,23 +192,36 @@ insert_citation <- function(bib_file = getOption("citr.bibliography_path")) {
# (yaml_found && !is.null(yaml_bib_file) && !isTRUE(all.equal(absolute_yaml_bib_file, getOption("citr.bibliography_path"))))
) {
withProgress({
if(!yaml_found || is.null(yaml_bib_file)) { # Use specified bibliography
if(betterbiblatex && reactive_variables$use_betterbiblatex) {

if(!is.null(input$update_bib)) options(citr.bibliography_path = input$update_bib)
current_bib <- load_zotero_bib()

} else {
if(!yaml_found || is.null(yaml_bib_file)) { # Use specified bibliography

if(!is.null(input$read_bib)) {
options(citr.bibliography_path = input$read_bib)
current_bib <- tryCatch(RefManageR::ReadBib(file = input$read_bib), error = error_handler)
} else {
current_bib <- tryCatch(RefManageR::ReadBib(file = getOption("citr.bibliography_path")), error = error_handler)
}

current_bib <- tryCatch(RefManageR::ReadBib(file = input$bib_file), error = error_handler)
options(citr.bibliography_path = input$bib_file)
} else if(yaml_found & !is.null(yaml_bib_file)) { # Use YAML bibliography, if available
} else if(yaml_found & !is.null(yaml_bib_file)) { # Use YAML bibliography, if available

if(length(yaml_bib_file) == 1) {
current_bib <- tryCatch(RefManageR::ReadBib(file = absolute_yaml_bib_file), error = error_handler)
} else {
bibs <- lapply(absolute_yaml_bib_file, function(file) tryCatch(RefManageR::ReadBib(file), error = error_handler))
options(citr.bibliography_path = absolute_yaml_bib_file)

## Merge if multiple bib files were imported succesfully
not_found <- sapply(bibs, is.null)
if(any(not_found)) warning("Unable to read bibliography file(s) ", paste(paste0("'", yaml_bib_file[not_found], "'"), collapse = ", "))
current_bib <- do.call(c, bibs[!not_found])
if(length(yaml_bib_file) == 1) {
current_bib <- tryCatch(RefManageR::ReadBib(file = absolute_yaml_bib_file), error = error_handler)
} else {
bibs <- lapply(absolute_yaml_bib_file, function(file) tryCatch(RefManageR::ReadBib(file), error = error_handler))

## Merge if multiple bib files were imported succesfully
not_found <- sapply(bibs, is.null)
if(any(not_found)) warning("Unable to read bibliography file(s) ", paste(paste0("'", yaml_bib_file[not_found], "'"), collapse = ", "))
current_bib <- do.call(c, bibs[!not_found])
}
}
options(citr.bibliography_path = absolute_yaml_bib_file)
}
}, message = "Loading bibliography...")

Expand Down Expand Up @@ -173,10 +251,37 @@ insert_citation <- function(bib_file = getOption("citr.bibliography_path")) {
current_key <- reactive({paste_citation_keys(input$selected_key, input$in_paren)})
output$rendered_key <- renderText({if(!is.null(current_key())) current_key() else "No reference selected."})

new_entries <- reactive({
if(betterbiblatex && reactive_variables$use_betterbiblatex) {
if(file.exists(input$update_bib)) {

existing_bib <- tryCatch(RefManageR::ReadBib(input$update_bib), error = error_handler)
if(length(existing_bib) > 0) {
new_references <- !input$selected_key %in% names(existing_bib)
} else {
new_references <- rep(TRUE, length(input$selected_key))
}

if(length(input$selected_key) > 0 && sum(new_references) > 0) {
return(bibliography()[key = paste0("^", input$selected_key[new_references], "$", collapse = "|")])
} else return(NULL)

} else {
return(bibliography()[key = paste0("^", input$selected_key, "$", collapse = "|")])
}
} else NULL
})

# Insert citation when button is clicked
observeEvent(
input$done
, {
# Update bib file
if(betterbiblatex && reactive_variables$use_betterbiblatex) {
if(!is.null(new_entries())) RefManageR::WriteBib(new_entries(), file = input$update_bib, append = TRUE)
}

# Insert citation
if(!(current_key() %in% c("[@]", "@"))) rstudioapi::insertText(current_key())
invisible(stopApp())
}
Expand Down Expand Up @@ -207,4 +312,4 @@ error_handler <- function(x) {
}

NULL
}
}
34 changes: 28 additions & 6 deletions R/md_cite.R
Expand Up @@ -5,9 +5,11 @@
#' @inheritParams query_bib
#' @param in_paren Logical. Determines if citation is in parentheses.
#'
#' @details The path to the Bib(La)TeX-file can be set in the global options and is set to
#' @details The path to the BibTeX-file can be set in the global options and is set to
#' \code{references.bib} when the package is loaded. Once the path is changed in the
#' RStudio addin, the global option is updated.
#' RStudio addin, the global option is updated. If \code{use_betterbiblatex = TRUE} references
#' are imported from Zotero rather than from the Bib(La)TeX-file. The Bib(La)TeX-file
#' is then updated to include the inserted reference.
#'
#' @return If the bibliography contains exactly one match the formated citation is returned, otherwise
#' returns \code{NULL}. \code{md_cite} returns an in-text citation (\code{"@foo2016"}), \code{md_cite}
Expand All @@ -26,15 +28,20 @@
md_cite <- function(
x
, in_paren = TRUE
, bib_file = options("citr.bibliography_path")
, bib_file = getOption("citr.bibliography_path")
, cache = TRUE
, use_betterbiblatex = getOption("citr.use_betterbiblatex")
) {
assert_that(is.flag(in_paren))

# Query BibTeX file
selected_entries <- query_bib(x, bib_file = bib_file, cache = cache)
selected_entries <- query_bib(
x
, bib_file = bib_file
, cache = cache
, use_betterbiblatex = use_betterbiblatex
)
if(length(selected_entries) == 0) return(NULL)
selected_keys <- names(selected_entries)

# Print queried references
tmp <- lapply(selected_entries, function(y) {
Expand All @@ -43,8 +50,13 @@ md_cite <- function(
})
cat("\n")

# Add references to bib_file
if(use_betterbiblatex && betterbiblatex_available()) {
append_bib_entries(selected_entries, bib_file)
}

# Return citation keys
paste_citation_keys(selected_keys, in_paren)
paste_citation_keys(names(selected_entries), in_paren)
}


Expand All @@ -69,4 +81,14 @@ paste_citation_keys <- function(keys, in_paren = FALSE) {

paste0("@", keys)
}
}

append_bib_entries <- function(x, bib_file) {
if(file.exists(bib_file)) {
existing_bib <- RefManageR::ReadBib(bib_file)
new_references <- !names(x) %in% names(existing_bib)
if(sum(new_references) > 0) RefManageR::WriteBib(x[new_references], file = bib_file, append = TRUE)
} else {
RefManageR::WriteBib(x, file = bib_file)
}
}

0 comments on commit 120f7fd

Please sign in to comment.