Skip to content

Commit

Permalink
Adds access to Zotero group libraries via BBT. Resolves #9
Browse files Browse the repository at this point in the history
  • Loading branch information
crsh committed Dec 19, 2017
1 parent 031be71 commit 6730d5a
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 60 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
.Rhistory
.RData
.Ruserdata

*.Rproj
*.Rproj
.DS_Store
10 changes: 6 additions & 4 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
Package: citr
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
Description: Functions and an 'RStudio' add-in that search a 'Bib(La)TeX'-file to create and
insert formatted Markdown citations into the current document.
Version: 0.2.0.9047
Date: 2016-9-20
Version: 0.2.0.9055
Date: 2017-12-17
Authors@R: c(
person("Frederik", "Aust", email = "frederik.aust@uni-koeln.de", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-4900-788X")),
person("Yihui", "Xie", role = "ctb")
Expand All @@ -22,9 +22,11 @@ Imports:
assertthat (>= 0.1),
methods (>= 3.0.0),
curl (>= 1.1),
httr (>= 1.3.1),
stringi (>= 1.1.2)
Suggests:
testthat (>= 1.0.2)
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: TRUE
RoxygenNote: 5.0.1
RoxygenNote: 6.0.1.9000
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
YEAR: 2016
COPYRIGHT HOLDER: Frederik Aust
COPYRIGHT HOLDER: Frederik Aust
73 changes: 63 additions & 10 deletions R/insert_citation.R
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
insert_citation <- function(
bib_file = getOption("citr.bibliography_path")
, use_betterbiblatex = getOption("citr.use_betterbiblatex")
, betterbiblatex_format = getOption("citr.betterbiblatex_format")
, encoding = getOption("citr.encoding")
) {
assert_that(is.character(bib_file))
Expand All @@ -45,6 +46,11 @@ insert_citation <- function(
} else stop("The use of this addin requires RStudio 0.99.796 or newer (your version is ", rstudioapi::versionInfo()$version, ").")

betterbiblatex <- betterbiblatex_available()
if(betterbiblatex) {
bbt_libraries <- query_bbt_libraries()
bbt_libraries_options <- unlist(bbt_libraries[, "name"])
names(bbt_libraries_options) <- unlist(bbt_libraries[, "name"])
}

# 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 Down Expand Up @@ -119,7 +125,7 @@ insert_citation <- function(
stableColumnLayout(
selectizeInput(
"selected_key"
, choices = c(`No references found` = "")
, choices = c("No references found" = "")
, label = ""
, width = 700
, multiple = TRUE
Expand All @@ -137,13 +143,14 @@ insert_citation <- function(
hr(),
uiOutput("bib_file"),
uiOutput("zotero_status"),
uiOutput("read_error")
uiOutput("read_error"),
uiOutput("bbt_libraries")
)
)

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

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

# Zotero use
observeEvent(input$disconnect_zotero, {
Expand All @@ -164,12 +171,18 @@ insert_citation <- function(
reactive_variables$reload_bib <- paste0(sample(letters, 100, replace = TRUE), collapse = "") # Do stuff to trigger reload_bib reactive
})

observeEvent(input$zotero_toggle_options, {
reactive_variables$zotero_options <- !reactive_variables$zotero_options
})

output$zotero_status <- renderUI({
if(betterbiblatex) {
if(reactive_variables$use_betterbiblatex) {
helpText(
"Connected to Zotero."
, actionLink("discard_cache", "Reload database")
, actionLink("discard_cache", if(length(bibliography()) == 0) "Load libraries" else "Reload libraries")
, "|"
, actionLink("zotero_toggle_options", if(!reactive_variables$zotero_options) "Select group libraries" else "Hide group libraries")
, "|"
, actionLink("disconnect_zotero", "Disconnect")
)
Expand All @@ -182,6 +195,20 @@ insert_citation <- function(
}
})

output$bbt_libraries <- renderUI({
if(betterbiblatex) {
if(length(bbt_libraries_options) > 1 && reactive_variables$use_betterbiblatex && reactive_variables$zotero_options) {
checkboxGroupInput(
"zotero_groups"
, label = NULL
, choices = as.list(bbt_libraries_options)
, selected = bbt_libraries_options[!bbt_libraries_options %in% c(getOption("citr.exclude_betterbiblatex_library"), "citr_dummy")]
, inline = TRUE
)
} else NULL
}
})

output$bib_file <- renderUI({
if(is.null(yaml_bib_file)) {
div(
Expand Down Expand Up @@ -238,19 +265,39 @@ insert_citation <- function(
is.null(getOption("citr.bibliography_cache")) || (is.character(getOption("citr.bibliography_cache")) && grepl("^Error: ", getOption("citr.bibliography_cache"))) #||
# (!is.null(yaml_bib_file) && !isTRUE(all.equal(absolute_yaml_bib_file, getOption("citr.bibliography_path"))))
) {
withProgress({
shiny::withProgress({
if(betterbiblatex && reactive_variables$use_betterbiblatex) {

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

if(reactive_variables$reload_bib == "init") {
current_bib <- c()
} else {
exclude_betterbiblatex_library <- c(bbt_libraries_options[!bbt_libraries_options %in% input$zotero_groups], "citr_dummy")

options("citr.exclude_betterbiblatex_library" = exclude_betterbiblatex_library)

if(!all(bbt_libraries_options %in% exclude_betterbiblatex_library)) {
current_bib <- load_betterbiblatex_bib(
encoding = encoding
, betterbiblatex_format = betterbiblatex_format
, exclude_betterbiblatex_library = exclude_betterbiblatex_library
, increment_progress = TRUE
)
} else {
current_bib <- c()
}
}

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

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

Expand All @@ -259,9 +306,14 @@ insert_citation <- function(
options(citr.bibliography_path = absolute_yaml_bib_file)

if(length(yaml_bib_file) == 1) {
setProgress(detail = basename(absolute_yaml_bib_file))
current_bib <- tryCatch(RefManageR::ReadBib(file = absolute_yaml_bib_file, check = FALSE, .Encoding = encoding), error = error_handler)
} else {
bibs <- lapply(absolute_yaml_bib_file, function(file) tryCatch(RefManageR::ReadBib(file, check = FALSE, .Encoding = encoding), error = error_handler))
bibs <- lapply(absolute_yaml_bib_file, function(file) {
setProgress(detail = basename(file))
tryCatch(RefManageR::ReadBib(file, check = FALSE, .Encoding = encoding), error = error_handler)
shiny::incProgress(1/length(absolute_yaml_bib_file))
})

## Merge if multiple bib files were imported succesfully
not_found <- sapply(bibs, is.null)
Expand All @@ -270,7 +322,7 @@ insert_citation <- function(
}
}
}
}, message = "Loading bibliography...")
}, message = "Loading Zotero libraries...")

## Cache bibliography
options(citr.bibliography_cache = current_bib)
Expand All @@ -290,9 +342,10 @@ insert_citation <- function(
citation_keys <- citation_keys[order(current_references)]
names(citation_keys) <- current_references[order(current_references)]

updateSelectInput(session, "selected_key", choices = c(`Search terms` = "", citation_keys), label = "")
updateSelectInput(session, "selected_key", choices = c("Search terms" = "", citation_keys), label = "")
} else {
updateSelectInput(session, "selected_key", c(`.bib-file(s) not found` = ""), label = "")
selected_key_default <- if(betterbiblatex && reactive_variables$use_betterbiblatex) c("No citations found. (Re-)Load Zotero libraries." = "") else c(".bib-file(s) not found" = "")
updateSelectInput(session, "selected_key", selected_key_default, label = "")
}
})

Expand Down
4 changes: 1 addition & 3 deletions R/md_cite_zotero.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#' Look up entries in Zotero database and insert citation in Markdown format.
#'
#' @param in_paren Logical. Determines if citation is in parentheses.
#' @param bib_file Character. Path to Bib(La)TeX-file. See details.
#'
#' @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
Expand All @@ -17,14 +16,13 @@
#'
#' @examples
#' \dontrun{
#' md_cite_zotero(bib_file = "references.bib")
#' md_cite_zotero()
#' }
#'
#' @import assertthat

md_cite_zotero <- function(
in_paren = TRUE
, bib_file = options("citr.bibliography_path")
) {
if(!betterbiblatex_available()) stop("Could not connect to Zotero's Better-BibTeX extension. Is Zotero up and running?")
assert_that(is.flag(in_paren))
Expand Down
2 changes: 2 additions & 0 deletions R/onload.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
op_citr <- list(
citr.bibliography_path = "./references.bib"
, citr.bibliography_cache = NULL
, citr.betterbiblatex_format = "bibtex"
, citr.use_betterbiblatex = TRUE
, citr.exclude_betterbiblatex_library = NULL
, citr.parent_documents = c("index.Rmd", "master.Rmd")
, citr.encoding = "UTF-8"
)
Expand Down
100 changes: 76 additions & 24 deletions R/query_bib.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
#' @param cache Logical. If \code{cache = TRUE} cached bibliography is used, if available. If
#' \code{cache = FALSE} bibliography is re-imported on every function call.
#' @param use_betterbiblatex Logical. If \code{use_betterbiblatex = TRUE} references are imported from Zotero/Juris-M.
#' @param betterbiblatex_format Charcter. Bibliography format to export from Zotero/Juris-M. Can be either \code{"bibtex"} or \code{"biblatex"}. Ignored if \code{use_betterbiblatex = FALSE}.
#' Requires that the \href{https://github.com/retorquere/zotero-better-bibtex}{Better Bib(La)TeX} is installed and
#' Zotero/Juris-M is running.
#' @param exclude_betterbiblatex_library Character. A vector of Zotero/Juris-M library names not to query.
#' @param encoding Character. Encoding of the Bib(La)TeX-file.
#'
#' @details The path to the BibTeX-file can be set in the global options and is set to
Expand All @@ -28,6 +30,8 @@ query_bib <- function(
, bib_file = getOption("citr.bibliography_path")
, cache = TRUE
, use_betterbiblatex = getOption("citr.use_betterbiblatex")
, betterbiblatex_format = getOption("citr.betterbiblatex_format")
, exclude_betterbiblatex_library = getOption("citr.exclude_betterbiblatex_library")
, encoding = getOption("citr.encoding")
) {
assert_that(is.string(x))
Expand All @@ -37,12 +41,16 @@ query_bib <- function(
if(use_betterbiblatex && !betterbiblatex_available()) {
message("Could not connect to Zotero's Better-BibTeX extension; importing references from", bib_file, ". Is Zotero up and running?")
}
assert_that(is.string(betterbiblatex_format))
if(!betterbiblatex_format %in% c("bibtex", "biblatex")) stop("Bibliography format not supported. Use either 'bibtex' or 'biblatex'.")
assert_that(is.string(encoding))

# Use cached bibliography, if available
if(is.null(getOption("citr.bibliography_cache")) || !cache) {

if(use_betterbiblatex & betterbiblatex_available()) {
bib <- load_betterbiblatex_bib(encoding)
cat("Connecting to Zotero...\n")
bib <- load_betterbiblatex_bib(encoding, betterbiblatex_format, exclude_betterbiblatex_library)
} else {
bib <- RefManageR::ReadBib(file = bib_file, check = FALSE, .Encoding = encoding)
}
Expand Down Expand Up @@ -127,38 +135,82 @@ betterbiblatex_available <- function() {
)
}

load_betterbiblatex_bib <- function(encoding) {
load_betterbiblatex_bib <- function(encoding, betterbiblatex_format = "bibtex", exclude_betterbiblatex_library = NULL, increment_progress = FALSE) {

bib <- c()
bbt_libraries <- query_bbt_libraries()

if(is.null(bbt_libraries)) {
betterbibtex_url <- paste0("http://localhost:23119/better-bibtex/library?library.", betterbiblatex_format)
bib <- import_bbt_piecewise(bib, betterbibtex_url, encoding, increment_progress)
} else {
betterbibtex_baseurl <- "http://localhost:23119/better-bibtex/library?/"
bbt_libraries <- bbt_libraries[!unlist(bbt_libraries[, "name"] %in% exclude_betterbiblatex_library), , drop = FALSE]
group_library_id <- unlist(bbt_libraries[, "id"])

for(bib_i in group_library_id) {
betterbibtex_url_i <- paste0(betterbibtex_baseurl, bib_i, "/library.", betterbiblatex_format)
group_library_name_i <- unlist(bbt_libraries[unlist(bbt_libraries[, "id"]) == bib_i, "name"])
if(increment_progress) {
setProgress(detail = paste0("\n'", group_library_name_i[[1]], "'"))
} else {
cat("Importing '", group_library_name_i, "'...\n", sep = "")
}

bib <- import_bbt_piecewise(
bib
, betterbibtex_url_i
, encoding
, increment_progress = increment_progress
, progress_share = 1 / length(group_library_id)
)
}
}

class(bib) <- c("BibEntry", "bibentry")
bib
}

# library("httr")
# bb_response <- POST(
# bb_library_url
# , add_headers("Content-Type" = "application/json")
# , body = '{"jsonrpc": "2.0", "method": "libraries" }'
# , encode = "json"
# )
#
# headers(bb_response)$`x-zotero-version`
# do.call(rbind, content(bb_response)$result)

betterbibtex_url <- "http://localhost:23119/better-bibtex/library?library.biblatex"
betterbibtex_bib <- rawToChar(curl::curl_fetch_memory(url = betterbibtex_url)$content)
betterbibtex_bib <- strsplit(betterbibtex_bib, "@comment\\{jabref-meta")[[1]][1] # Remove jab-ref comments
betterbibtex_entries <- strsplit(gsub("(@\\w+\\{)", "~!citr!~\\1", betterbibtex_bib), "~!citr!~" )[[1]]
import_bbt_piecewise <- function(x, location, encoding, increment_progress, progress_share = 1) {
betterbibtex_bib_i <- rawToChar(curl::curl_fetch_memory(url = location)$content)
betterbibtex_bib_i <- strsplit(betterbibtex_bib_i, "@comment\\{jabref-meta")[[1]][1] # Remove jab-ref comments
betterbibtex_entries_i <- strsplit(gsub("(@\\w+\\{)", "~!citr!~\\1", betterbibtex_bib_i), "~!citr!~" )[[1]]

# Create and read multiple biblatex files because bibtex::read.bib does not work with large files
bib <- c()
no_batches <- length(betterbibtex_entries) %/% 100 + 1
no_batches <- length(betterbibtex_entries_i) %/% 100 + 1

for(i in seq_len(no_batches)) {
for(j in seq_len(no_batches)) {
tmp_bib_file <- paste0(paste(sample(c(letters, LETTERS, 0:9), size = 32, replace = TRUE), collapse = ""), ".bib")

writeLines(betterbibtex_entries[((i-1) * 100 + 1):min(i * 100, length(betterbibtex_entries))], con = file(tmp_bib_file, encoding = encoding))
bib <- c(bib, RefManageR::ReadBib(file = tmp_bib_file, check = FALSE))
tmp_con <- file(tmp_bib_file, encoding = encoding)
writeLines(betterbibtex_entries_i[((j-1) * 100 + 1):min(j * 100, length(betterbibtex_entries_i))], con = tmp_con)
close(tmp_con)

x <- c(x, RefManageR::ReadBib(file = tmp_bib_file, check = FALSE, .Encoding = encoding))
file.remove(tmp_bib_file)

if(increment_progress) shiny::incProgress((1 / no_batches) * progress_share)
}

try(on.exit(file.remove(tmp_bib_file)))
try(on.exit(suppressWarnings(file.remove(tmp_bib_file))), silent = TRUE)
x
}

class(bib) <- c("BibEntry", "bibentry")
bib

query_bbt_libraries <- function(x) {
# If installed BBT is >= 5.0.50 use JSON-RPC to qurey library IDs
bbt_json_response <- httr::POST(
"http://localhost:23119/better-bibtex/json-rpc"
, httr::add_headers("Content-Type" = "application/json")
, body = '{"jsonrpc": "2.0", "method": "user.groups" }'
, encode = "json"
)

if(any(grepl("No endpoint found", httr::content(bbt_json_response)))) {
NULL
} else {
do.call(rbind, httr::content(bbt_json_response)$result)
}
}

2 changes: 1 addition & 1 deletion inst/rstudio/addins.dcf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Name: Insert citations
Description: Inserts Markdown citations
Binding: insert_citation
Interactive: true
Interactive: true
Loading

0 comments on commit 6730d5a

Please sign in to comment.