-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathstyle.R
210 lines (188 loc) · 7.15 KB
/
style.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#' The grkstyle Code Style
#'
#' Use [styler::style_text()] to format code according to the unofficial
#' \pkg{grkstyle} style guide. Follows the
#' [tidyverse style guide](https://style.tidyverse.org) as implemented by
#' [styler::tidyverse_style()], but with additional style rules that enforce
#' consistent line breaks inside function calls and left-justification of
#' function arguments in function definitions.
#'
#' @section Using the \pkg{grkstyle} code style: You can set the \pkg{grkstyle}
#' code style as the default code style for \pkg{styler} (and its associated
#' RStudio addins, like "Style active file" and "Style selection") by calling
#' `grkstyle::use_grk_style()`. If you would rather set this option globally
#' for all session, you can add the following to your `.Rprofile`:
#'
#' ```
#' options(styler.addins_style_transformer = "grkstyle::grk_style_transformer()")
#' ```
#'
#' @examples
#' \dontrun{
#' use_grk_style()
#' # Use styler addins
#' styler:::style_selection()
#' }
#'
#' ex_code <- "mtcars %>% mutate(mpg = mpg * 2,\n\t\t cyl = paste(cyl)) %>% head()"
#' cat(ex_code)
#'
#' grk_style_text(ex_code)
#' @param ... Arguments passed to underling \pkg{styler} functions (identified
#' by removing the `grk_` prefix), except for `transformers`, which is set to
#' the `grk_style_transformer()` internally.
#' @name grk_style
NULL
#' @describeIn grk_style Set the \pkg{grkstyle} style as the default style for
#' \pkg{styler} addins.
#' @export
use_grk_style <- function() {
options(styler.addins_style_transformer = "grkstyle::grk_style_transformer()")
}
#' @describeIn grk_style A code transformer for use with [styler::style_text()]
#' @param use_tabs Should a single tab be used for indentation? \pkg{grkstyle}
#' functions will by default follow the `grkstyle.use_tabs` option, if set, or
#' will attempt to look up the project settings in the current RStudio
#' project. You can choose to use tabs or spaces for a project by changing the
#' _Insert spaces for tab_ option in the Code Editing panel of the Project
#' Options settings.
#'
#' If not otherwise set via the R option or the RStudio project option, tabs
#' are used. Tabs are recommended because they are
#' [more accessible](https://alexandersandberg.com/articles/default-to-tabs-instead-of-spaces-for-an-accessible-first-environment/).
#'
#' You can opt into indentation by two spaces by setting the
#' `grkstyle.use_tabs` option:
#'
#' ```r
#' # two spaces
#' options(grkstyle.use_tabs = FALSE)
#'
#' # equivalently, but more precise
#' options(grkstyle.use_tabs = list(indent_by = 2, indent_character = " "))
#' ```
#' @export
grk_style_transformer <- function(
...,
use_tabs = getOption("grkstyle.use_tabs", NULL)
) {
use_tabs <- grk_use_tabs(use_tabs)
tidy_style <- styler::tidyverse_style(
indent_by = use_tabs$indent_by
)
tidy_style$indent_character <- use_tabs$indent_character
tidy_style$style_guide_name <- "grkstyle::grk_style_transformer@https://github.com/gadenbuie"
tidy_style$style_guide_version <- pkg_version()
# line breaks between *all* arguments if line breaks between *any*
tidy_style$line_break$set_linebreak_each_argument_if_multi_line <- function(pd) {
if (!(any(pd$token == "','"))) {
return(pd)
}
# does this expression contain expressions with linebreaks?
has_children <- purrr::some(pd$child, purrr::negate(is.null))
has_internal_linebreak <- FALSE
is_function_definition <- pd$token[1] == "FUNCTION"
if (has_children && !is_function_definition) {
children <- pd$child
# don't count anything inside {} as internal linebreaks
idx_pre_open_brace <- which(pd$token_after == "'{'")
if (length(idx_pre_open_brace)) {
children[idx_pre_open_brace + 1] <- NULL
}
has_internal_linebreak <- children %>%
purrr::discard(is.null) %>%
purrr::map_lgl(function(x) {
sum(x$newlines, x$lag_newlines) > 0
}) %>%
any()
}
if (!has_internal_linebreak && sum(pd$newlines, pd$lag_newlines) < 1) {
return(pd)
}
idx_comma <- which(pd$token == "','")
idx_open_paren <- grep("'[[(]'", pd$token)
idx_close_paren <- grep("'(]|\\))'", pd$token)
idx_comma_has_comment <- which(pd$token[idx_comma + 1] == "COMMENT")
idx_comma[idx_comma_has_comment] <- idx_comma[idx_comma_has_comment] + 1
pd[idx_comma + 1L, "lag_newlines"] <- 1L
if (length(idx_open_paren)) {
pd[idx_open_paren[1] + 1L, "lag_newlines"] <- 1L
}
if (length(idx_close_paren)) {
pd[idx_close_paren[length(idx_close_paren)], "lag_newlines"] <- 1L
}
pd
}
# Function arguments on new lines, indented with 2 spaces
tidy_style$indention$update_indention_ref_fun_dec <- function(pd_nested) {
if (pd_nested$token[1] == "FUNCTION" && nrow(pd_nested) > 4) {
seq <- seq.int(3L, nrow(pd_nested) - 2L)
pd_nested$indention_ref_pos_id[seq] <- 0L
pd_nested$indent[seq] <- use_tabs$indent_by
}
pd_nested
}
# Disable styler's extra indentation of closing `) {` in function declarations
# https://github.com/gadenbuie/grkstyle/issues/10
styler_unindent_fun_dec <- tidy_style$indention$unindent_fun_dec
tidy_style$indention$unindent_fun_dec <- function(pd) {
styler_unindent_fun_dec(pd, indent_by = 0)
}
tidy_style
}
grk_use_tabs <- function(use_tabs = getOption("grkstyle.use_tabs", NULL)) {
if (is.null(use_tabs)) {
root <- tryCatch(
rprojroot::find_rstudio_root_file(),
error = function(...) NULL
)
if (is.null(root)) {
return(grk_use_tabs(TRUE))
}
rproj <- file.path(root, dir(root, pattern = "[.]Rproj$"))
rproj <- readLines(rproj, warn = FALSE)
if (any(grepl("^UseSpacesForTab: No$", rproj))) {
return(grk_use_tabs(TRUE))
}
indent_by_chr <- grep("^NumSpacesForTab: \\d+", rproj, value = TRUE)
if (!length(indent_by_chr)) {
return(grk_use_tabs(FALSE))
}
indent_by <- as.numeric(sub("NumSpacesForTab: ", "", indent_by_chr))
return(grk_use_tabs(list(indent_by = indent_by, indent_character = " ")))
}
if (isTRUE(use_tabs)) {
use_tabs <- list(indent_by = 1L, indent_character = "\t")
} else if (identical(use_tabs, FALSE)) {
use_tabs <- list(indent_by = 2L, indent_character = " ")
} else if (!setequal(c("indent_by", "indent_character"), names(use_tabs))) {
stop(
"`use_tabs` should be one of `TRUE`, `FALSE`, or a list containing ",
"items 'intent_by' or 'indent_character'."
)
}
use_tabs
}
#' @describeIn grk_style Style text using the \pkg{grkstyle} code style
#' @inheritParams styler::style_text
#' @export
grk_style_text <- function(text, ...) {
styler::style_text(text, ..., transformers = grk_style_transformer())
}
#' @describeIn grk_style Style a file using the \pkg{grkstyle} code style
#' @inheritParams styler::style_file
#' @export
grk_style_file <- function(path, ...) {
styler::style_file(path, ..., transformers = grk_style_transformer())
}
#' @describeIn grk_style Style a directory using the \pkg{grkstyle} code style
#' @export
grk_style_dir <- function(path, ...) {
styler::style_dir(path, ..., transformers = grk_style_transformer())
}
#' @describeIn grk_style Style a package using the \pkg{grkstyle} code style
#' @inheritParams styler::style_pkg
#' @export
grk_style_pkg <- function(pkg = ".", ...) {
styler::style_pkg(pkg, ..., transformers = grk_style_transformer())
}