132 | ```
133 |
134 | ```{r,eval=TRUE}
135 |
136 | #' @field name Name of the module, either generated for or provided by the user.
137 | n$name
138 | ta$name
139 |
140 | #' @field group Group name of the module.
141 | n$group
142 | ta$group
143 |
144 | #' @field id ID of the module.
145 | n$id
146 | ta$id
147 |
148 | #' @field parent_ns Parent module namespace in case of nested modules.
149 | n$parent_ns
150 | ta$parent_ns
151 |
152 | #' @field module_ns Module namespace, unique identifier for the module.
153 | n$module_ns
154 | ta$module_ns
155 | ```
156 |
157 | Like in conventional module, `{tm}` module also requires wrapping UI elements with the namespace.
158 | The function named `ns()` available in all `{tm}` modules should be used to namespace the inputs.
159 | As you can see in the example below, you simply need to call the function using the `self` R6 keyword.
160 | There is no need to remember the modules's namespace anymore.
161 |
162 |
163 | ```{r,eval=TRUE}
164 |
165 | MyMod <- R6::R6Class(
166 | "MyMod",
167 | inherit = TidyModule,
168 | public = list(
169 | ui = function() {
170 | shiny::tagList(
171 | shiny::numericInput(self$ns("inputId"), NULL, 0)
172 | )
173 | }
174 | )
175 | )
176 |
177 | m <- MyMod$new()
178 | as.character(m$ui())
179 | ```
180 |
181 | ## `ModStore` and module lookup
182 |
183 | The `ModStore` is an internal repository for all `{tm}` modules and connections (see [communication article](communication.html) for learning how to connect modules). It is a shared environment created by `{tidymodules}` that orginizes the objects (modules and edges) by applications and sessions.
184 | This allows to track and easily retrieve the modules anywhere in the application.
185 |
186 | All the examples above show the creation of modules with the `new()` R6 function and the assignment to variables (pointers to the R6 objects).
187 | However `{tidymodules}` also offers the choice to not save module references and instead use the `getMod()` or `mod()` utility functions to retrieve existing module. Note that `mod()` is just an alias of `getMod()`.
188 |
189 | ```{r,eval=TRUE}
190 |
191 | MyMod$new("SaveMeInStore")
192 |
193 | # look-up by namespace ID
194 | mod("SaveMeInStore")
195 |
196 | # look-up by index
197 | mod(2)
198 |
199 | # look-up by index within a group
200 | mod(1, group = "A")
201 | ```
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
--------------------------------------------------------------------------------
/vignettes/tidymodules.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | pagetitle: "Introduction to tidymodules"
3 | title: "Introduction to tidymodules"
4 | author: "Xiao Ni, Mustapha Larbaoui"
5 | date: "`r Sys.Date()`"
6 | output: rmarkdown::html_vignette
7 | vignette: >
8 | %\VignetteIndexEntry{Introduction to tidymodules}
9 | %\VignetteEngine{knitr::rmarkdown}
10 | %\VignetteEncoding{UTF-8}
11 | ---
12 |
13 | ```{r setup, include = FALSE}
14 | knitr::opts_chunk$set(
15 | collapse = TRUE,
16 | comment = "#>"
17 | )
18 | ```
19 |
20 | ## Overview
21 |
22 | This vignette aims to provide a high level introduction to tidymodules. We recommend reading this article for anyone who is new to tidymodules, especially those tidymodules module "consumers", who use existing module classes as a "black-box" in their Shiny app development. This article includes the following topics:
23 |
24 | - TidyModule ports: key infrastructure for managing cross-module communication
25 | - How to use tidymodules modules as a Shiny app developer and tidymodules consumer
26 | - Existing examples
27 |
28 | If you would like to develop new tidymodules modules, please refer to the vignettes under "Articles".
29 |
30 | ## TidyModule structure: ports
31 |
32 | ### Introducing ports
33 |
34 | In conventional Shiny modules, communication between modules is realized through the following
35 |
36 | - **Input:** passing reactives as functional parameters into the Shiny `callModule()` function
37 | - **Output:** returning reactives in the module `server()` function.
38 |
39 | It can be challenging to keep track of the arbitrary number of input and output reactives for complex apps with many interacting modules.
40 |
41 | To address this challenge, we introduced input/output ports as fields of the TidyModule class object in tidymodules.
42 | The concept is illustrated in the following diagram. In this example, Module1 and Module2 each have input and output ports that hold reactives.
43 | The ports defined in each modules provides a data structure that allow TidyModule to establish a communication between them.
44 |
45 | 
46 |
47 | The two modules are connected via a tidymodules pipe operator `%x>y%` where x and y could be any numbers from 1 to 10. For example,`%1>1%` means the left module's first output port (x = 1) is mapped to the first input port (y = 1) of the right module. Multiple connected tidymodules modules with such directed edges form a directed graph network, which will be further discussed later in this article.
48 |
49 | ### Finding out ports of a tidymodules module
50 |
51 | To find out the port structure, simply print the module object on the console. The following example shows that the `Addition` module has one input port named "left" and one output port named "total" that are both empty, i.e. not being assigned an input reactive (input ports) or injected into the server code (output ports).
52 | ```{r}
53 | library(shiny)
54 | library(tidymodules)
55 | source(system.file(package = "tidymodules", "shiny/examples/1_simple_addition/Addition.R"))
56 | Addition$new()
57 | ```
58 |
59 |
60 | ## Using tidymodules modules
61 |
62 | The basic workflow of using tidymodules modules in a Shiny app is the following
63 |
64 | - Load tidymodules module class definition
65 | - Identify module structure such as input/output ports, ui(), and server() functionalities
66 | - Instantiate new module objects from tidymodules classes
67 | - Construct app `ui()` using module `ui()` methods
68 | - In app `server()` inject tidymodules module `server()` logic using `callModules()` or the `callModule()` function of the module object, like `myMod$callModule()`.
69 | - Set up module communication/relationship via tidymodules pipe operators and functions.
70 |
71 | The workflow is illustrated in the following example, which is available at
72 |
73 | Example 1: Simple addition [](https://tidymodules.shinyapps.io/1_simple_addition/)
74 |
75 | ### Load module definition
76 |
77 | ```{r, eval=FALSE}
78 | library(tidymodules)
79 | # source tidymodules Addition module definition
80 | source(system.file(package = "tidymodules", "shiny/examples/1_simple_addition/Addition.R"))
81 | ```
82 |
83 | ### Instantiate module objects
84 |
85 | Notice that the namespace argument in `$new()` is optional and `tidymodules` will automatically generate a namespace ID if not provided.
86 | ```{r, eval=T}
87 | # Instantiate two Addition module objects
88 | Addition$new()
89 | Addition$new()
90 | ```
91 | Also notice that it is not necessary to give a name to the `Addition$new()` object. `tidymodules` provides `mod()` or `getMod()` function to help users conveniently retrieve module objects via their numerical ID or namespace ID.
92 |
93 | ### Adding `ui()`
94 |
95 | In the app `ui()`, we call the `ui()` method of each module object.
96 | ```{r, eval=FALSE}
97 | ui <- fixedPage(
98 | h2("tidymodules : Addition example"),
99 | shiny::fluidRow(
100 | sliderInput("first_number", label = "Enter your first number", min = 1, max = 100, value = 1), br(),
101 |
102 | # Calling module ui() methods
103 | mod(1)$ui(), br(),
104 | mod(2)$ui(), br(),
105 | "Total: ", textOutput("total_result")
106 | )
107 | )
108 | ```
109 |
110 | ### Add module `server()` logic using `callModules()`
111 | Here we use the `callModules()` function to call the server() methods for the two modules that we created.
112 | ```{r, eval=FALSE}
113 | server <- function(input, output, session) {
114 | # call the server() functions for all existing tidymodules modules in the global environment
115 | callModules()
116 | }
117 | ```
118 |
119 | ### Establish cross-module communication via 'tidy' pipe operators
120 |
121 | The module communication is established through the pipe operators: `first %>1% mod(1) %1>1% mod(2)`. Note that in `first` must be a Shiny reactive value or endpoint in order to server as an input to other tidymodules modules.
122 | ```{r, eval=FALSE}
123 | server <- function(input, output, session) {
124 | # call the server() functions for all existing tidymodules modules in the global environment
125 | callModules()
126 |
127 | first <- reactive({
128 | req(input$first_number)
129 | })
130 |
131 | # Setting up module commmunication
132 | observe({
133 | first %>1% mod(1) %1>1% mod(2)
134 | })
135 |
136 | output$total_result <- renderText({
137 | result <- mod(2)$getOutput(1)
138 | result()
139 | })
140 | }
141 |
142 | shinyApp(ui, server)
143 | ```
144 |
145 | We also provide utility functions to help identify and connect ports using the port names. For more information about the pipe operators, refer to the functional documentation under "Reference" tab.
146 |
147 | ### Module relational network
148 |
149 | Tidymodules module objects are mananged by `ModStore` in tidymodules. For example, in the
150 | example 2 - Linkled scatter [](https://tidymodules.shinyapps.io/2_linked_scatter/) you can find the sessions, module objects, edges and port mapping diagram in the "Help | ModStore" tab.
151 |
152 | Below is the module relationship network digram generated by `tidymodules` using the `visNetwork` package.
153 |
154 |
155 |
156 | ### Inheritance and Entity Relational Diagram
157 |
158 | For more details about class and ports inheritance, see article [inheritance](inheritance.html).
159 | The diagram below illustrates the relation between the classes defined in the example 4 of tidymodules [](https://tidymodules.shinyapps.io/4_communication/).
160 |
161 | 
162 |
163 | ## Other examples
164 |
165 | You can list all Shiny examples that come with the `tidymodules` package by the `showExamples()` function. We recommend going through these examples to help you understand the use patterns.
166 | ```{r}
167 | showExamples()
168 | ```
169 |
170 | We have already used the first example to illustrate the basic usage of tidymodules, below we briefly describe the other examples.
171 |
172 | ### Example 2: linked scatter plot
173 |
174 | This example illustrates the tidymodules implementation of the classical Shiny module example of [two linked scatter plots](https://shiny.rstudio.com/gallery/module-example.html).
175 |
176 | ### Example 3: nested modules
177 |
178 | This example [](https://tidymodules.shinyapps.io/3_nested_modules/) illustrates constructing and using nested modules in the `tidymodules` framework, as well as dynamically creating tidymodules modules.
179 |
180 | ### Example 4: module communication
181 |
182 | This is a comprehensive example [](https://tidymodules.shinyapps.io/4_communication/) to illustrate mutiple advanced features such as
183 |
184 | - Inheritance
185 | - Port operations: `combine_ports()`
186 | - Enable/disable module communication
187 |
--------------------------------------------------------------------------------
/vignettes/intro.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | pagetitle: "Getting started with writing `{tidymodules}`"
3 | title: "Getting started with writing `{tidymodules}`"
4 | subtitle: "
"
5 | author: "Xiao Ni, Mustapha Larbaoui"
6 | date: "`r Sys.Date()`"
7 | output: rmarkdown::html_vignette
8 | vignette: >
9 | %\VignetteIndexEntry{Getting started with writing `{tidymodules}`}
10 | %\VignetteEngine{knitr::rmarkdown}
11 | %\VignetteEncoding{UTF-8}
12 | ---
13 |
14 | ```{r setup, include = FALSE}
15 | knitr::opts_chunk$set(
16 | collapse = TRUE,
17 | eval = TRUE,
18 | comment = "#>"
19 | )
20 | ```
21 |
22 | ## A quick introduction to R6 and Object Oriented Programming (OOP)
23 |
24 | `{tidymodules}` or `{tm}` in short, is based on R6 ([https://r6.r-lib.org/](https://r6.r-lib.org/)), which is an implementation of encapsulated object-oriented programming for R. Therefore knowledge of R6 is a prerequisite to develop `{tm}` modules.
25 |
26 | R6 provides a framework for OOP in R. Unlike the functional programming style, R6 encapsulates methods and fields in classes that instantiate into objects. R6 classes are similar to R's reference classes, but are more efficient and do not depend on S4 classes and the methods package.
27 |
28 | This vignette provides a brief overview of R6 for developers new to R6. For more information, developers are recommended to review the R6 packagedown site ([https://r6.r-lib.org/](https://r6.r-lib.org/)), as well as Chapter 14 of the Advanced R book ([https://adv-r.hadley.nz/r6.html](https://adv-r.hadley.nz/r6.html))
29 |
30 | ### R6 classes and methods
31 |
32 | `{tm}` depends on the R6 package which you can install from CRAN and load:
33 | ```{r , eval=FALSE}
34 | library(R6)
35 | ```
36 |
37 | R6 classes are created using the [`R6::R6Class()` function](https://r6.r-lib.org/reference/R6Class.html), which is the only function from the R6 package that is typically used. The following is a simple example of defining an R6 class:
38 | ```{r, eval=TRUE}
39 | Calculator <- R6::R6Class(
40 | classname = "Calculator",
41 | public = list(
42 | value = NA,
43 | initialize = function(value) {
44 | self$value <- value
45 | },
46 | add = function(x = 1) {
47 | self$value <- self$value + x
48 | invisible(self)
49 | }
50 | )
51 | )
52 | ```
53 | The first argument `classname` by convention uses `UpperCamelCase`. The second argument `public` encapsulates a list of methods (functions) and fields (objects) that make up the public interface of the object. By convention methods and fields use `snake_case`. Methods can access the methods and fields of the current object using `self$`. One should always assign the result of `R6Class()` into a variable with the same names as the `classname` because `R6Class()` returns an R6 object that defines the class.
54 |
55 |
56 | You can print the class definition:
57 | ```{r}
58 | Calculator
59 | ```
60 |
61 |
62 | To create a new instance of `Calculator`, use the `$new()` method. The `$initialize()` is an important method, which overrides the default behavior of `$new()`. In the above example, the `$initialize()` method initializes the `calculator1` object with `value = 0`.
63 | ```{r}
64 | calculator1 <- Calculator$new(0)
65 | calculator1
66 | ```
67 |
68 |
69 | You can then call the methods and access fields using `$`:
70 | ```{r}
71 | calculator1$add(10)
72 | calculator1$value
73 | ```
74 |
75 | You can also add methods after class creation as illustrated below for the existing `Calculator` R6 class, although new methods and fields are only available to new objects.
76 | ```{r}
77 | Calculator$set("public", "subtract", function(x = 1) {
78 | self$value <- self$value - x
79 | invisible(self)
80 | })
81 | Calculator
82 | ```
83 |
84 |
85 | Below are some key features of R6.
86 |
87 | - **Reference semantics**: objects are not copied when modified. R6 provides a `$clone()` method for making copy of an object. For more details, refer to https://r6.r-lib.org/reference/R6Class.html#cloning-objects.
88 | - **Public vs. private members**: `R6Class()` has a `private` argument for you to define private methods and fileds that can only be accessed from within the class, not from the outside.
89 | - **Inheritance**: as in classical OOP, one R6 class can inherit from another R6 class. Superclass methods can be accessed with `super$`.
90 |
91 | ## `tidymodules::TidyModule` class
92 |
93 | The `tidymodules::TidyModule` class is a R6 class and the parent of all `{tm}` modules.
94 |
95 | Below is partial code of the `TidyModule` class for illustration purpose. The `TidyModule` class includes many public methods. There are utility functions such as `callModules()`, `definePorts()`, `assignPort()` as well as functions that need to be overwritten such as `ui()`, `server()`, etc.
96 |
97 | Unlike conventional Shiny modules in funtional programming style, `{tm}` encapsulates functions such as ui() and server() as methods in a TidyModule class object. Module namespace ID is seamlessly managed within the module class for the ui and server. For complete technical documentation that includes other methods and fields, see `?TidyModule`.
98 | ```{r, eval=FALSE}
99 | TidyModule <- R6::R6Class(
100 | "TidyModule",
101 | public = list(
102 | id = NULL,
103 | module_ns = NULL,
104 | parent_ns = NULL,
105 | parent_mod = NULL,
106 | parent_ports = NULL,
107 | group = NULL,
108 | created = NULL,
109 | o = NULL,
110 | i = NULL,
111 | initialize = function(id = NULL, inherit = TRUE, group = NULL) {
112 | # details omitted
113 | },
114 | # Other methods such
115 | ui = function() {
116 | return(shiny::tagList())
117 | },
118 | server = function(input,
119 | output,
120 | session) {
121 | # Need to isolate this block to avoid unecessary triggers
122 | shiny::isolate({
123 | private$shiny_session <- session
124 | private$shiny_input <- input
125 | private$shiny_output <- output
126 | })
127 | },
128 | definePort = function(x) {
129 | shiny::isolate(x)
130 | },
131 | assignPort = function(x) {
132 | shiny::observe({
133 | shiny::isolate(x)
134 | })
135 | },
136 | # Other public methods omitted
137 | ),
138 | private = list(
139 | # Details omitted
140 | )
141 | )
142 | ```
143 |
144 |
145 |
146 | ## Writing your first `{tm}` module
147 |
148 | You can develop new `{tm}` modules by inheriting and extending the `tidymodules::TidyModule` class.
149 |
150 | Below is a minimal example, `RandomNumberGenerator`, defined with one input port and one output port. The input port is a random number seed that feeds into a random number generator, whose result serves as the module output.
151 |
152 | ```{r}
153 | # Module definition
154 | RandomNumMod <- R6::R6Class(
155 | "RandomNumGenerator",
156 | inherit = tidymodules::TidyModule,
157 | public = list(
158 | initialize = function(id = NULL) {
159 | super$initialize(id)
160 |
161 | self$definePort({
162 | self$addInputPort(
163 | name = "seed",
164 | description = "random number seed",
165 | sample = 123
166 | )
167 |
168 | self$addOutputPort(
169 | name = "number",
170 | description = "Random number",
171 | sample = 123
172 | )
173 | })
174 | },
175 | ui = function() {
176 | tagList(
177 | verbatimTextOutput(self$ns("text"))
178 | )
179 | },
180 | server = function(input, output, session) {
181 | super$server(input, output, session)
182 |
183 | result <- reactive({
184 | s <- self$getInput("seed")
185 | set.seed(s())
186 | floor(runif(1) * 1e5)
187 | })
188 |
189 | output$text <- renderPrint({
190 | s <- self$getInput("seed")
191 | print(paste0("seed = ", s()))
192 | print(paste0("number = ", result()))
193 | })
194 |
195 | self$assignPort({
196 | self$updateOutputPort(
197 | id = "number",
198 | output = result
199 | )
200 | })
201 | return(result)
202 | }
203 | )
204 | )
205 | ```
206 |
207 | Cross-communication between two `{tm}` modules is established using several flavours of the pipe `%>%` operator, as illustrated in the following code. The first module's output is fed as the random number seed for the second module.
208 | ```{r, eval=FALSE}
209 | ## Calling app
210 | randomNumMod1 <- RandomNumMod$new()
211 | randomNumMod2 <- RandomNumMod$new()
212 |
213 | ui <- tagList(
214 | fluidPage(
215 | randomNumMod1$ui(),
216 | randomNumMod2$ui()
217 | )
218 | )
219 | server <- function(input, output, session) {
220 | randomNumMod1$callModule()
221 | randomNumMod2$callModule()
222 |
223 | seed_1 <- reactive(123)
224 |
225 | observe({
226 | seed_1 %>1% randomNumMod1 %1>1% randomNumMod2
227 | })
228 | }
229 |
230 | shinyApp(ui = ui, server = server)
231 | ```
232 |
233 | ## Next steps
234 |
235 | To learn more about writing `{tm}` modules, read the examples.
236 |
237 |
--------------------------------------------------------------------------------
/R/add_module.R:
--------------------------------------------------------------------------------
1 | #' Create a module
2 | #'
3 | #' This function creates a `{tm}` module class inside the current folder.
4 | #'
5 | #' @param name The class name of the module.
6 | #' @param path Where to created the file. Default is `getwd()`. The function will add `R` to the path if the sub-folder exists.
7 | #' @param prefix filename prefix. Default is `tm`. Set to `NULL`` to disable.
8 | #' @param inherit Parent module class. Default is TidyModule.
9 | #' @param open Should the file be opened?
10 | #' @param dir_create Creates the directory if it doesn't exist, default is `TRUE`.
11 | #' @param export Logical. Should the module be exported? Default is `FALSE`.
12 | #' @note As a convention, this function will automatically capitalize the first character of the `name` argument.
13 | #'
14 | #' @importFrom cli cat_bullet
15 | #' @importFrom utils file.edit
16 | #' @importFrom fs path_abs path file_create
17 | #' @importFrom snippr snippets_read
18 | #'
19 | #' @export
20 | add_module <- function(name,
21 | inherit = "TidyModule",
22 | path = getwd(),
23 | prefix = "tm",
24 | open = TRUE,
25 | dir_create = TRUE,
26 | export = FALSE) {
27 | name <- file_path_sans_ext(name)
28 | # Capitalize
29 | name <- paste0(toupper(substring(name, 1, 1)), substring(name, 2))
30 |
31 | dir_created <- create_if_needed(
32 | fs::path(path),
33 | type = "directory"
34 | )
35 | if (!dir_created) {
36 | cat_red_bullet(
37 | "File not added (needs a valid directory)"
38 | )
39 | return(invisible(FALSE))
40 | }
41 |
42 | if (dir.exists(fs::path(path, "R"))) {
43 | path <- fs::path(path, "R")
44 | }
45 |
46 | old <- setwd(path_abs(path))
47 | on.exit(setwd(old))
48 |
49 | where <- fs::path(
50 | paste0(ifelse(is.null(prefix), "", paste0(prefix, "_")), name, ".R")
51 | )
52 |
53 | if (!check_file_exist(where)) {
54 | cat_red_bullet(
55 | "File not created (already exists)"
56 | )
57 | return(invisible(FALSE))
58 | }
59 |
60 | # make sure the provided parent module is valid
61 | import <- NULL
62 | parent <- inherit
63 | # TidyModule object
64 | if (is(parent, "TidyModule")) {
65 | parent <- class(parent)[1]
66 | }
67 | # Load the class generator from the name
68 | if (class(parent) == "character") {
69 | tryCatch(
70 | {
71 | parent <- eval(parse(text = parent))
72 | },
73 | error = function(e) {
74 | cat_red_bullet(
75 | paste0("Could not find module defined with 'inherit' = ", inherit)
76 | )
77 | return(invisible(FALSE))
78 | }
79 | )
80 | }
81 | # Retrieve package dependency and parent module name
82 | if (is(parent, "R6ClassGenerator")) {
83 | clist <- get_R6CG_list(parent)
84 | if ("TidyModule" %in% clist) {
85 | import <- environmentName(parent$parent_env)
86 | if (import == "R_GlobalEnv") {
87 | import <- NULL
88 | }
89 | parent <- clist[1]
90 | } else {
91 | cat_red_bullet(
92 | paste0("Could not find module defined with 'inherit' = ", deparse(substitute(inherit)))
93 | )
94 | return(invisible(FALSE))
95 | }
96 | }
97 |
98 |
99 | # Retrieve content from package snippet
100 | file_content <- snippr::snippets_read(path = system.file("rstudio/r.snippets", package = "tidymodules"))$tm.mod.new
101 | file_content <- unlist(strsplit(file_content, "\\n"))
102 | for (l in seq_len(length(file_content))) {
103 | # remove $ escapes \\
104 | file_content[l] <- sub("\\$", "$", file_content[l], fixed = TRUE)
105 | # remove tabs
106 | file_content[l] <- sub("\t", "", file_content[l])
107 | # remove snippet placeholders
108 | file_content[l] <- gsub("\\$\\{\\d+:(\\w+)\\}", "%\\1", file_content[l])
109 | # remove cursor pointer
110 | file_content[l] <- sub("\\$\\{0\\}", "", file_content[l])
111 | # substitute module name
112 | if (grepl("MyModule", file_content[l])) {
113 | file_content[l] <- gsub("MyModule", "s", file_content[l])
114 | file_content[l] <- sprintf(file_content[l], name)
115 | }
116 | # substitute parent module
117 | if (grepl("TidyModule", file_content[l])) {
118 | file_content[l] <- gsub("TidyModule", "s", file_content[l])
119 | file_content[l] <- sprintf(file_content[l], parent)
120 | }
121 | # manage export
122 | if (grepl("@export", file_content[l])) {
123 | if (!export) {
124 | file_content[l] <- "#' @noRd "
125 | }
126 | if (!is.null(import)) {
127 | file_content[l] <- paste0("#'\n#' @import ", import, "\n", file_content[l])
128 | }
129 | }
130 | }
131 | writeLines(file_content, where, sep = "\n")
132 |
133 | cat_created(fs::path(path, where))
134 | open_or_go_to(where, open)
135 | }
136 |
137 | # bunch of utility functions copied from golem
138 | # WILL FACILITATE MIGRATING THIS FUNCTION TO GOLEM
139 |
140 | #' @importFrom utils menu
141 | yesno <- function(...) {
142 | cat(paste0(..., collapse = ""))
143 | menu(c("Yes", "No")) == 1
144 | }
145 |
146 | #' @importFrom fs file_exists
147 | check_file_exist <- function(file) {
148 | res <- TRUE
149 | if (file_exists(file)) {
150 | cat_orange_bullet(file)
151 | res <- yesno("This file already exists, override?")
152 | }
153 | return(res)
154 | }
155 |
156 | #' @importFrom fs dir_create file_create
157 | create_if_needed <- function(path,
158 | type = c("file", "directory"),
159 | content = NULL) {
160 | type <- match.arg(type)
161 | # Check if file or dir already exist
162 | if (type == "file") {
163 | dont_exist <- file_not_exist(path)
164 | } else if (type == "directory") {
165 | dont_exist <- dir_not_exist(path)
166 | }
167 | # If it doesn't exist, ask if we are allowed
168 | # to create it
169 | if (dont_exist) {
170 | ask <- yesno(
171 | sprintf(
172 | "The %s %s doesn't exist, create?",
173 | basename(path),
174 | type
175 | )
176 | )
177 | # Return early if the user doesn't allow
178 | if (!ask) {
179 | return(FALSE)
180 | } else {
181 | # Create the file
182 | if (type == "file") {
183 | if (dir_not_exist(dirname(path))) {
184 | dir_create(dirname(path), recurse = TRUE)
185 | }
186 | file_create(path)
187 | write(content, path, append = TRUE)
188 | } else if (type == "directory") {
189 | dir_create(path, recurse = TRUE)
190 | }
191 | }
192 | }
193 |
194 | # TRUE means that file exists (either
195 | # created or already there)
196 | return(TRUE)
197 | }
198 |
199 |
200 | #' @importFrom cli cat_bullet
201 | cat_green_tick <- function(...) {
202 | cat_bullet(
203 | ...,
204 | bullet = "tick",
205 | bullet_col = "green"
206 | )
207 | }
208 |
209 | #' @importFrom cli cat_bullet
210 | cat_red_bullet <- function(...) {
211 | cat_bullet(
212 | ...,
213 | bullet = "bullet",
214 | bullet_col = "red"
215 | )
216 | }
217 |
218 | #' @importFrom cli cat_bullet
219 | cat_orange_bullet <- function(...) {
220 | cat_bullet(
221 | ...,
222 | bullet = "bullet",
223 | bullet_col = "orange"
224 | )
225 | }
226 |
227 | #' @importFrom cli cat_bullet
228 | cat_info <- function(...) {
229 | cat_bullet(
230 | ...,
231 | bullet = "arrow_right",
232 | bullet_col = "grey"
233 | )
234 | }
235 |
236 | #' @importFrom fs path_file
237 | cat_exists <- function(where) {
238 | cat_red_bullet(
239 | sprintf(
240 | "%s already exists, skipping the copy.",
241 | path_file(where)
242 | )
243 | )
244 | cat_info(
245 | sprintf(
246 | "If you want replace it, remove the %s file first.",
247 | path_file(where)
248 | )
249 | )
250 | }
251 |
252 | cat_created <- function(where,
253 | file = "File") {
254 | cat_green_tick(
255 | sprintf(
256 | "%s created at %s",
257 | file,
258 | where
259 | )
260 | )
261 | }
262 |
263 | open_or_go_to <- function(where,
264 | open) {
265 | if (
266 | rstudioapi::isAvailable() &&
267 | open &&
268 | rstudioapi::hasFun("navigateToFile")
269 | ) {
270 | rstudioapi::navigateToFile(where)
271 | } else {
272 | cat_red_bullet(
273 | sprintf(
274 | "Go to %s",
275 | where
276 | )
277 | )
278 | }
279 | }
280 |
281 | desc_exist <- function(pkg) {
282 | file_exists(
283 | paste0(pkg, "/DESCRIPTION")
284 | )
285 | }
286 |
287 |
288 | file_created_dance <- function(where,
289 | fun,
290 | pkg,
291 | dir,
292 | name,
293 | open) {
294 | cat_created(where)
295 |
296 | fun(pkg, dir, name)
297 |
298 | open_or_go_to(where, open)
299 | }
300 |
301 | if_not_null <- function(x, ...) {
302 | if (!is.null(x)) {
303 | force(...)
304 | }
305 | }
306 |
307 | set_name <- function(x, y) {
308 | names(x) <- y
309 | x
310 | }
311 |
312 | # FROM tools::file_path_sans_ext() & tools::file_ext
313 | file_path_sans_ext <- function(x) {
314 | sub("([^.]+)\\.[[:alnum:]]+$", "\\1", x)
315 | }
316 |
317 | file_ext <- function(x) {
318 | pos <- regexpr("\\.([[:alnum:]]+)$", x)
319 | ifelse(pos > -1L, substring(x, pos + 1L), "")
320 | }
321 |
322 | #' @importFrom fs dir_exists file_exists
323 | dir_not_exist <- Negate(dir_exists)
324 | file_not_exist <- Negate(file_exists)
325 |
--------------------------------------------------------------------------------
/man/ModStore.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/ModStore.R
3 | \name{ModStore}
4 | \alias{ModStore}
5 | \title{R6 Class Representing a ModStore}
6 | \description{
7 | This class is used to create a storage for tidymodules objects.
8 | }
9 | \details{
10 | Manage applications, sessions and modules.
11 | }
12 | \examples{
13 |
14 | ## ------------------------------------------------
15 | ## Method `ModStore$new`
16 | ## ------------------------------------------------
17 |
18 | MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule)
19 | m <- MyModule$new()
20 | s <- m$getStore()
21 |
22 | ## ------------------------------------------------
23 | ## Method `ModStore$isStored`
24 | ## ------------------------------------------------
25 |
26 | MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule)
27 | m <- MyModule$new()
28 | s <- m$getStore()
29 | s$isStored(m)
30 | }
31 | \section{Methods}{
32 | \subsection{Public methods}{
33 | \itemize{
34 | \item \href{#method-ModStore-new}{\code{ModStore$new()}}
35 | \item \href{#method-ModStore-isStored}{\code{ModStore$isStored()}}
36 | \item \href{#method-ModStore-getGlobalSession}{\code{ModStore$getGlobalSession()}}
37 | \item \href{#method-ModStore-getSession}{\code{ModStore$getSession()}}
38 | \item \href{#method-ModStore-getSessions}{\code{ModStore$getSessions()}}
39 | \item \href{#method-ModStore-getMods}{\code{ModStore$getMods()}}
40 | \item \href{#method-ModStore-getEdges}{\code{ModStore$getEdges()}}
41 | \item \href{#method-ModStore-addEdge}{\code{ModStore$addEdge()}}
42 | \item \href{#method-ModStore-delEdges}{\code{ModStore$delEdges()}}
43 | \item \href{#method-ModStore-addMod}{\code{ModStore$addMod()}}
44 | \item \href{#method-ModStore-delMod}{\code{ModStore$delMod()}}
45 | \item \href{#method-ModStore-print}{\code{ModStore$print()}}
46 | \item \href{#method-ModStore-clone}{\code{ModStore$clone()}}
47 | }
48 | }
49 | \if{html}{\out{
}}
50 | \if{html}{\out{}}
51 | \if{latex}{\out{\hypertarget{method-ModStore-new}{}}}
52 | \subsection{Method \code{new()}}{
53 | Create a new ModStore object.
54 | Should be called once by the TidyModule class.
55 | Not to be called directly outside TidyModule.
56 | The ModStore object can be retrieved from any TidyModule object, see example below.
57 | \subsection{Usage}{
58 | \if{html}{\out{}}\preformatted{ModStore$new()}\if{html}{\out{
}}
59 | }
60 |
61 | \subsection{Returns}{
62 | A new \code{ModStore} object.
63 | }
64 | \subsection{Examples}{
65 | \if{html}{\out{}}
66 | \preformatted{MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule)
67 | m <- MyModule$new()
68 | s <- m$getStore()
69 | }
70 | \if{html}{\out{
}}
71 |
72 | }
73 |
74 | }
75 | \if{html}{\out{
}}
76 | \if{html}{\out{}}
77 | \if{latex}{\out{\hypertarget{method-ModStore-isStored}{}}}
78 | \subsection{Method \code{isStored()}}{
79 | Check if a module is stored in the current session.
80 | \subsection{Usage}{
81 | \if{html}{\out{}}\preformatted{ModStore$isStored(m)}\if{html}{\out{
}}
82 | }
83 |
84 | \subsection{Arguments}{
85 | \if{html}{\out{}}
86 | \describe{
87 | \item{\code{m}}{TidyModule object.}
88 | }
89 | \if{html}{\out{
}}
90 | }
91 | \subsection{Examples}{
92 | \if{html}{\out{}}
93 | \preformatted{MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule)
94 | m <- MyModule$new()
95 | s <- m$getStore()
96 | s$isStored(m)
97 | }
98 | \if{html}{\out{
}}
99 |
100 | }
101 |
102 | }
103 | \if{html}{\out{
}}
104 | \if{html}{\out{}}
105 | \if{latex}{\out{\hypertarget{method-ModStore-getGlobalSession}{}}}
106 | \subsection{Method \code{getGlobalSession()}}{
107 | Retrieve the global session 'global_session'.
108 | This is the session that exists outside the application server function
109 | \subsection{Usage}{
110 | \if{html}{\out{}}\preformatted{ModStore$getGlobalSession()}\if{html}{\out{
}}
111 | }
112 |
113 | }
114 | \if{html}{\out{
}}
115 | \if{html}{\out{}}
116 | \if{latex}{\out{\hypertarget{method-ModStore-getSession}{}}}
117 | \subsection{Method \code{getSession()}}{
118 | Retrieve a module session.
119 | This could be the global session or a user session.
120 | \subsection{Usage}{
121 | \if{html}{\out{}}\preformatted{ModStore$getSession(m)}\if{html}{\out{
}}
122 | }
123 |
124 | \subsection{Arguments}{
125 | \if{html}{\out{}}
126 | \describe{
127 | \item{\code{m}}{TidyModule object.}
128 | }
129 | \if{html}{\out{
}}
130 | }
131 | }
132 | \if{html}{\out{
}}
133 | \if{html}{\out{}}
134 | \if{latex}{\out{\hypertarget{method-ModStore-getSessions}{}}}
135 | \subsection{Method \code{getSessions()}}{
136 | Retrieve all sessions.
137 | \subsection{Usage}{
138 | \if{html}{\out{}}\preformatted{ModStore$getSessions()}\if{html}{\out{
}}
139 | }
140 |
141 | }
142 | \if{html}{\out{
}}
143 | \if{html}{\out{}}
144 | \if{latex}{\out{\hypertarget{method-ModStore-getMods}{}}}
145 | \subsection{Method \code{getMods()}}{
146 | Retrieve all modules.
147 | \subsection{Usage}{
148 | \if{html}{\out{}}\preformatted{ModStore$getMods(m)}\if{html}{\out{
}}
149 | }
150 |
151 | \subsection{Arguments}{
152 | \if{html}{\out{}}
153 | \describe{
154 | \item{\code{m}}{TidyModule object.}
155 | }
156 | \if{html}{\out{
}}
157 | }
158 | }
159 | \if{html}{\out{
}}
160 | \if{html}{\out{}}
161 | \if{latex}{\out{\hypertarget{method-ModStore-getEdges}{}}}
162 | \subsection{Method \code{getEdges()}}{
163 | Retrieve modules connections.
164 | \subsection{Usage}{
165 | \if{html}{\out{}}\preformatted{ModStore$getEdges(m)}\if{html}{\out{
}}
166 | }
167 |
168 | \subsection{Arguments}{
169 | \if{html}{\out{}}
170 | \describe{
171 | \item{\code{m}}{TidyModule object.}
172 | }
173 | \if{html}{\out{
}}
174 | }
175 | }
176 | \if{html}{\out{
}}
177 | \if{html}{\out{}}
178 | \if{latex}{\out{\hypertarget{method-ModStore-addEdge}{}}}
179 | \subsection{Method \code{addEdge()}}{
180 | Add modules connections into ModStore.
181 | An edge is either a connection between a reactive object and a module
182 | or between two modules.
183 | \subsection{Usage}{
184 | \if{html}{\out{}}\preformatted{ModStore$addEdge(from, to, mode = "direct", comment = NA)}\if{html}{\out{
}}
185 | }
186 |
187 | \subsection{Arguments}{
188 | \if{html}{\out{}}
189 | \describe{
190 | \item{\code{from}}{list with three elements: m -> module, type -> input or output, port -> port Id.}
191 |
192 | \item{\code{to}}{list with three elements: m -> module, type -> input or output, port -> port Id.}
193 |
194 | \item{\code{mode}}{The type of edge, default to 'direct'.}
195 |
196 | \item{\code{comment}}{Any additional comment.}
197 | }
198 | \if{html}{\out{
}}
199 | }
200 | }
201 | \if{html}{\out{
}}
202 | \if{html}{\out{}}
203 | \if{latex}{\out{\hypertarget{method-ModStore-delEdges}{}}}
204 | \subsection{Method \code{delEdges()}}{
205 | Remove module edges
206 | \subsection{Usage}{
207 | \if{html}{\out{}}\preformatted{ModStore$delEdges(m)}\if{html}{\out{
}}
208 | }
209 |
210 | \subsection{Arguments}{
211 | \if{html}{\out{}}
212 | \describe{
213 | \item{\code{m}}{TidyModule object.}
214 | }
215 | \if{html}{\out{
}}
216 | }
217 | }
218 | \if{html}{\out{
}}
219 | \if{html}{\out{}}
220 | \if{latex}{\out{\hypertarget{method-ModStore-addMod}{}}}
221 | \subsection{Method \code{addMod()}}{
222 | Add module into the ModStore.
223 | \subsection{Usage}{
224 | \if{html}{\out{}}\preformatted{ModStore$addMod(m)}\if{html}{\out{
}}
225 | }
226 |
227 | \subsection{Arguments}{
228 | \if{html}{\out{}}
229 | \describe{
230 | \item{\code{m}}{TidyModule object.}
231 | }
232 | \if{html}{\out{
}}
233 | }
234 | }
235 | \if{html}{\out{
}}
236 | \if{html}{\out{}}
237 | \if{latex}{\out{\hypertarget{method-ModStore-delMod}{}}}
238 | \subsection{Method \code{delMod()}}{
239 | Delete a module from the ModStore.
240 | \subsection{Usage}{
241 | \if{html}{\out{}}\preformatted{ModStore$delMod(m)}\if{html}{\out{
}}
242 | }
243 |
244 | \subsection{Arguments}{
245 | \if{html}{\out{}}
246 | \describe{
247 | \item{\code{m}}{TidyModule object.}
248 | }
249 | \if{html}{\out{
}}
250 | }
251 | }
252 | \if{html}{\out{
}}
253 | \if{html}{\out{}}
254 | \if{latex}{\out{\hypertarget{method-ModStore-print}{}}}
255 | \subsection{Method \code{print()}}{
256 | Print the ModStore object.
257 | \subsection{Usage}{
258 | \if{html}{\out{}}\preformatted{ModStore$print()}\if{html}{\out{
}}
259 | }
260 |
261 | }
262 | \if{html}{\out{
}}
263 | \if{html}{\out{}}
264 | \if{latex}{\out{\hypertarget{method-ModStore-clone}{}}}
265 | \subsection{Method \code{clone()}}{
266 | The objects of this class are cloneable with this method.
267 | \subsection{Usage}{
268 | \if{html}{\out{}}\preformatted{ModStore$clone(deep = FALSE)}\if{html}{\out{
}}
269 | }
270 |
271 | \subsection{Arguments}{
272 | \if{html}{\out{}}
273 | \describe{
274 | \item{\code{deep}}{Whether to make a deep clone.}
275 | }
276 | \if{html}{\out{
}}
277 | }
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Apache License
2 | ==============
3 |
4 | _Version 2.0, January 2004_
5 | _<>_
6 |
7 | ### Terms and Conditions for use, reproduction, and distribution
8 |
9 | #### 1. Definitions
10 |
11 | “License” shall mean the terms and conditions for use, reproduction, and
12 | distribution as defined by Sections 1 through 9 of this document.
13 |
14 | “Licensor” shall mean the copyright owner or entity authorized by the copyright
15 | owner that is granting the License.
16 |
17 | “Legal Entity” shall mean the union of the acting entity and all other entities
18 | that control, are controlled by, or are under common control with that entity.
19 | For the purposes of this definition, “control” means **(i)** the power, direct or
20 | indirect, to cause the direction or management of such entity, whether by
21 | contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
22 | outstanding shares, or **(iii)** beneficial ownership of such entity.
23 |
24 | “You” (or “Your”) shall mean an individual or Legal Entity exercising
25 | permissions granted by this License.
26 |
27 | “Source” form shall mean the preferred form for making modifications, including
28 | but not limited to software source code, documentation source, and configuration
29 | files.
30 |
31 | “Object” form shall mean any form resulting from mechanical transformation or
32 | translation of a Source form, including but not limited to compiled object code,
33 | generated documentation, and conversions to other media types.
34 |
35 | “Work” shall mean the work of authorship, whether in Source or Object form, made
36 | available under the License, as indicated by a copyright notice that is included
37 | in or attached to the work (an example is provided in the Appendix below).
38 |
39 | “Derivative Works” shall mean any work, whether in Source or Object form, that
40 | is based on (or derived from) the Work and for which the editorial revisions,
41 | annotations, elaborations, or other modifications represent, as a whole, an
42 | original work of authorship. For the purposes of this License, Derivative Works
43 | shall not include works that remain separable from, or merely link (or bind by
44 | name) to the interfaces of, the Work and Derivative Works thereof.
45 |
46 | “Contribution” shall mean any work of authorship, including the original version
47 | of the Work and any modifications or additions to that Work or Derivative Works
48 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
49 | by the copyright owner or by an individual or Legal Entity authorized to submit
50 | on behalf of the copyright owner. For the purposes of this definition,
51 | “submitted” means any form of electronic, verbal, or written communication sent
52 | to the Licensor or its representatives, including but not limited to
53 | communication on electronic mailing lists, source code control systems, and
54 | issue tracking systems that are managed by, or on behalf of, the Licensor for
55 | the purpose of discussing and improving the Work, but excluding communication
56 | that is conspicuously marked or otherwise designated in writing by the copyright
57 | owner as “Not a Contribution.”
58 |
59 | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf
60 | of whom a Contribution has been received by Licensor and subsequently
61 | incorporated within the Work.
62 |
63 | #### 2. Grant of Copyright License
64 |
65 | Subject to the terms and conditions of this License, each Contributor hereby
66 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
67 | irrevocable copyright license to reproduce, prepare Derivative Works of,
68 | publicly display, publicly perform, sublicense, and distribute the Work and such
69 | Derivative Works in Source or Object form.
70 |
71 | #### 3. Grant of Patent License
72 |
73 | Subject to the terms and conditions of this License, each Contributor hereby
74 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
75 | irrevocable (except as stated in this section) patent license to make, have
76 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
77 | such license applies only to those patent claims licensable by such Contributor
78 | that are necessarily infringed by their Contribution(s) alone or by combination
79 | of their Contribution(s) with the Work to which such Contribution(s) was
80 | submitted. If You institute patent litigation against any entity (including a
81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
82 | Contribution incorporated within the Work constitutes direct or contributory
83 | patent infringement, then any patent licenses granted to You under this License
84 | for that Work shall terminate as of the date such litigation is filed.
85 |
86 | #### 4. Redistribution
87 |
88 | You may reproduce and distribute copies of the Work or Derivative Works thereof
89 | in any medium, with or without modifications, and in Source or Object form,
90 | provided that You meet the following conditions:
91 |
92 | * **(a)** You must give any other recipients of the Work or Derivative Works a copy of
93 | this License; and
94 | * **(b)** You must cause any modified files to carry prominent notices stating that You
95 | changed the files; and
96 | * **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
97 | all copyright, patent, trademark, and attribution notices from the Source form
98 | of the Work, excluding those notices that do not pertain to any part of the
99 | Derivative Works; and
100 | * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
101 | Derivative Works that You distribute must include a readable copy of the
102 | attribution notices contained within such NOTICE file, excluding those notices
103 | that do not pertain to any part of the Derivative Works, in at least one of the
104 | following places: within a NOTICE text file distributed as part of the
105 | Derivative Works; within the Source form or documentation, if provided along
106 | with the Derivative Works; or, within a display generated by the Derivative
107 | Works, if and wherever such third-party notices normally appear. The contents of
108 | the NOTICE file are for informational purposes only and do not modify the
109 | License. You may add Your own attribution notices within Derivative Works that
110 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
111 | provided that such additional attribution notices cannot be construed as
112 | modifying the License.
113 |
114 | You may add Your own copyright statement to Your modifications and may provide
115 | additional or different license terms and conditions for use, reproduction, or
116 | distribution of Your modifications, or for any such Derivative Works as a whole,
117 | provided Your use, reproduction, and distribution of the Work otherwise complies
118 | with the conditions stated in this License.
119 |
120 | #### 5. Submission of Contributions
121 |
122 | Unless You explicitly state otherwise, any Contribution intentionally submitted
123 | for inclusion in the Work by You to the Licensor shall be under the terms and
124 | conditions of this License, without any additional terms or conditions.
125 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
126 | any separate license agreement you may have executed with Licensor regarding
127 | such Contributions.
128 |
129 | #### 6. Trademarks
130 |
131 | This License does not grant permission to use the trade names, trademarks,
132 | service marks, or product names of the Licensor, except as required for
133 | reasonable and customary use in describing the origin of the Work and
134 | reproducing the content of the NOTICE file.
135 |
136 | #### 7. Disclaimer of Warranty
137 |
138 | Unless required by applicable law or agreed to in writing, Licensor provides the
139 | Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
140 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
141 | including, without limitation, any warranties or conditions of TITLE,
142 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
143 | solely responsible for determining the appropriateness of using or
144 | redistributing the Work and assume any risks associated with Your exercise of
145 | permissions under this License.
146 |
147 | #### 8. Limitation of Liability
148 |
149 | In no event and under no legal theory, whether in tort (including negligence),
150 | contract, or otherwise, unless required by applicable law (such as deliberate
151 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
152 | liable to You for damages, including any direct, indirect, special, incidental,
153 | or consequential damages of any character arising as a result of this License or
154 | out of the use or inability to use the Work (including but not limited to
155 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
156 | any and all other commercial damages or losses), even if such Contributor has
157 | been advised of the possibility of such damages.
158 |
159 | #### 9. Accepting Warranty or Additional Liability
160 |
161 | While redistributing the Work or Derivative Works thereof, You may choose to
162 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
163 | other liability obligations and/or rights consistent with this License. However,
164 | in accepting such obligations, You may act only on Your own behalf and on Your
165 | sole responsibility, not on behalf of any other Contributor, and only if You
166 | agree to indemnify, defend, and hold each Contributor harmless for any liability
167 | incurred by, or claims asserted against, such Contributor by reason of your
168 | accepting any such warranty or additional liability.
169 |
170 | _END OF TERMS AND CONDITIONS_
171 |
--------------------------------------------------------------------------------
/NAMESPACE:
--------------------------------------------------------------------------------
1 | # Generated by roxygen2: do not edit by hand
2 |
3 | export("%->%")
4 | export("%->>%")
5 | export("%1<1%")
6 | export("%2<1%")
7 | export("%3<1%")
8 | export("%4<1%")
9 | export("%5<1%")
10 | export("%6<1%")
11 | export("%7<1%")
12 | export("%8<1%")
13 | export("%9<1%")
14 | export("%10<1%")
15 | export("%1<2%")
16 | export("%2<2%")
17 | export("%3<2%")
18 | export("%4<2%")
19 | export("%5<2%")
20 | export("%6<2%")
21 | export("%7<2%")
22 | export("%8<2%")
23 | export("%9<2%")
24 | export("%10<2%")
25 | export("%1<3%")
26 | export("%2<3%")
27 | export("%3<3%")
28 | export("%4<3%")
29 | export("%5<3%")
30 | export("%6<3%")
31 | export("%7<3%")
32 | export("%8<3%")
33 | export("%9<3%")
34 | export("%10<3%")
35 | export("%1<4%")
36 | export("%2<4%")
37 | export("%3<4%")
38 | export("%4<4%")
39 | export("%5<4%")
40 | export("%6<4%")
41 | export("%7<4%")
42 | export("%8<4%")
43 | export("%9<4%")
44 | export("%10<4%")
45 | export("%1<5%")
46 | export("%2<5%")
47 | export("%3<5%")
48 | export("%4<5%")
49 | export("%5<5%")
50 | export("%6<5%")
51 | export("%7<5%")
52 | export("%8<5%")
53 | export("%9<5%")
54 | export("%10<5%")
55 | export("%1<6%")
56 | export("%2<6%")
57 | export("%3<6%")
58 | export("%4<6%")
59 | export("%5<6%")
60 | export("%6<6%")
61 | export("%7<6%")
62 | export("%8<6%")
63 | export("%9<6%")
64 | export("%10<6%")
65 | export("%1<7%")
66 | export("%2<7%")
67 | export("%3<7%")
68 | export("%4<7%")
69 | export("%5<7%")
70 | export("%6<7%")
71 | export("%7<7%")
72 | export("%8<7%")
73 | export("%9<7%")
74 | export("%10<7%")
75 | export("%1<8%")
76 | export("%2<8%")
77 | export("%3<8%")
78 | export("%4<8%")
79 | export("%5<8%")
80 | export("%6<8%")
81 | export("%7<8%")
82 | export("%8<8%")
83 | export("%9<8%")
84 | export("%10<8%")
85 | export("%1<9%")
86 | export("%2<9%")
87 | export("%3<9%")
88 | export("%4<9%")
89 | export("%5<9%")
90 | export("%6<9%")
91 | export("%7<9%")
92 | export("%8<9%")
93 | export("%9<9%")
94 | export("%10<9%")
95 | export("%1<10%")
96 | export("%2<10%")
97 | export("%3<10%")
98 | export("%4<10%")
99 | export("%5<10%")
100 | export("%6<10%")
101 | export("%7<10%")
102 | export("%8<10%")
103 | export("%9<10%")
104 | export("%10<10%")
105 | export("%1<<1%")
106 | export("%2<<1%")
107 | export("%3<<1%")
108 | export("%4<<1%")
109 | export("%5<<1%")
110 | export("%6<<1%")
111 | export("%7<<1%")
112 | export("%8<<1%")
113 | export("%9<<1%")
114 | export("%10<<1%")
115 | export("%1<<2%")
116 | export("%2<<2%")
117 | export("%3<<2%")
118 | export("%4<<2%")
119 | export("%5<<2%")
120 | export("%6<<2%")
121 | export("%7<<2%")
122 | export("%8<<2%")
123 | export("%9<<2%")
124 | export("%10<<2%")
125 | export("%1<<3%")
126 | export("%2<<3%")
127 | export("%3<<3%")
128 | export("%4<<3%")
129 | export("%5<<3%")
130 | export("%6<<3%")
131 | export("%7<<3%")
132 | export("%8<<3%")
133 | export("%9<<3%")
134 | export("%10<<3%")
135 | export("%1<<4%")
136 | export("%2<<4%")
137 | export("%3<<4%")
138 | export("%4<<4%")
139 | export("%5<<4%")
140 | export("%6<<4%")
141 | export("%7<<4%")
142 | export("%8<<4%")
143 | export("%9<<4%")
144 | export("%10<<4%")
145 | export("%1<<5%")
146 | export("%2<<5%")
147 | export("%3<<5%")
148 | export("%4<<5%")
149 | export("%5<<5%")
150 | export("%6<<5%")
151 | export("%7<<5%")
152 | export("%8<<5%")
153 | export("%9<<5%")
154 | export("%10<<5%")
155 | export("%1<<6%")
156 | export("%2<<6%")
157 | export("%3<<6%")
158 | export("%4<<6%")
159 | export("%5<<6%")
160 | export("%6<<6%")
161 | export("%7<<6%")
162 | export("%8<<6%")
163 | export("%9<<6%")
164 | export("%10<<6%")
165 | export("%1<<7%")
166 | export("%2<<7%")
167 | export("%3<<7%")
168 | export("%4<<7%")
169 | export("%5<<7%")
170 | export("%6<<7%")
171 | export("%7<<7%")
172 | export("%8<<7%")
173 | export("%9<<7%")
174 | export("%10<<7%")
175 | export("%1<<8%")
176 | export("%2<<8%")
177 | export("%3<<8%")
178 | export("%4<<8%")
179 | export("%5<<8%")
180 | export("%6<<8%")
181 | export("%7<<8%")
182 | export("%8<<8%")
183 | export("%9<<8%")
184 | export("%10<<8%")
185 | export("%1<<9%")
186 | export("%2<<9%")
187 | export("%3<<9%")
188 | export("%4<<9%")
189 | export("%5<<9%")
190 | export("%6<<9%")
191 | export("%7<<9%")
192 | export("%8<<9%")
193 | export("%9<<9%")
194 | export("%10<<9%")
195 | export("%1<<10%")
196 | export("%2<<10%")
197 | export("%3<<10%")
198 | export("%4<<10%")
199 | export("%5<<10%")
200 | export("%6<<10%")
201 | export("%7<<10%")
202 | export("%8<<10%")
203 | export("%9<<10%")
204 | export("%10<<10%")
205 | export("%1>1%")
206 | export("%2>1%")
207 | export("%3>1%")
208 | export("%4>1%")
209 | export("%5>1%")
210 | export("%6>1%")
211 | export("%7>1%")
212 | export("%8>1%")
213 | export("%9>1%")
214 | export("%10>1%")
215 | export("%1>2%")
216 | export("%2>2%")
217 | export("%3>2%")
218 | export("%4>2%")
219 | export("%5>2%")
220 | export("%6>2%")
221 | export("%7>2%")
222 | export("%8>2%")
223 | export("%9>2%")
224 | export("%10>2%")
225 | export("%1>3%")
226 | export("%2>3%")
227 | export("%3>3%")
228 | export("%4>3%")
229 | export("%5>3%")
230 | export("%6>3%")
231 | export("%7>3%")
232 | export("%8>3%")
233 | export("%9>3%")
234 | export("%10>3%")
235 | export("%1>4%")
236 | export("%2>4%")
237 | export("%3>4%")
238 | export("%4>4%")
239 | export("%5>4%")
240 | export("%6>4%")
241 | export("%7>4%")
242 | export("%8>4%")
243 | export("%9>4%")
244 | export("%10>4%")
245 | export("%1>5%")
246 | export("%2>5%")
247 | export("%3>5%")
248 | export("%4>5%")
249 | export("%5>5%")
250 | export("%6>5%")
251 | export("%7>5%")
252 | export("%8>5%")
253 | export("%9>5%")
254 | export("%10>5%")
255 | export("%1>6%")
256 | export("%2>6%")
257 | export("%3>6%")
258 | export("%4>6%")
259 | export("%5>6%")
260 | export("%6>6%")
261 | export("%7>6%")
262 | export("%8>6%")
263 | export("%9>6%")
264 | export("%10>6%")
265 | export("%1>7%")
266 | export("%2>7%")
267 | export("%3>7%")
268 | export("%4>7%")
269 | export("%5>7%")
270 | export("%6>7%")
271 | export("%7>7%")
272 | export("%8>7%")
273 | export("%9>7%")
274 | export("%10>7%")
275 | export("%1>8%")
276 | export("%2>8%")
277 | export("%3>8%")
278 | export("%4>8%")
279 | export("%5>8%")
280 | export("%6>8%")
281 | export("%7>8%")
282 | export("%8>8%")
283 | export("%9>8%")
284 | export("%10>8%")
285 | export("%1>9%")
286 | export("%2>9%")
287 | export("%3>9%")
288 | export("%4>9%")
289 | export("%5>9%")
290 | export("%6>9%")
291 | export("%7>9%")
292 | export("%8>9%")
293 | export("%9>9%")
294 | export("%10>9%")
295 | export("%1>10%")
296 | export("%2>10%")
297 | export("%3>10%")
298 | export("%4>10%")
299 | export("%5>10%")
300 | export("%6>10%")
301 | export("%7>10%")
302 | export("%8>10%")
303 | export("%9>10%")
304 | export("%10>10%")
305 | export("%1>>1%")
306 | export("%2>>1%")
307 | export("%3>>1%")
308 | export("%4>>1%")
309 | export("%5>>1%")
310 | export("%6>>1%")
311 | export("%7>>1%")
312 | export("%8>>1%")
313 | export("%9>>1%")
314 | export("%10>>1%")
315 | export("%1>>2%")
316 | export("%2>>2%")
317 | export("%3>>2%")
318 | export("%4>>2%")
319 | export("%5>>2%")
320 | export("%6>>2%")
321 | export("%7>>2%")
322 | export("%8>>2%")
323 | export("%9>>2%")
324 | export("%10>>2%")
325 | export("%1>>3%")
326 | export("%2>>3%")
327 | export("%3>>3%")
328 | export("%4>>3%")
329 | export("%5>>3%")
330 | export("%6>>3%")
331 | export("%7>>3%")
332 | export("%8>>3%")
333 | export("%9>>3%")
334 | export("%10>>3%")
335 | export("%1>>4%")
336 | export("%2>>4%")
337 | export("%3>>4%")
338 | export("%4>>4%")
339 | export("%5>>4%")
340 | export("%6>>4%")
341 | export("%7>>4%")
342 | export("%8>>4%")
343 | export("%9>>4%")
344 | export("%10>>4%")
345 | export("%1>>5%")
346 | export("%2>>5%")
347 | export("%3>>5%")
348 | export("%4>>5%")
349 | export("%5>>5%")
350 | export("%6>>5%")
351 | export("%7>>5%")
352 | export("%8>>5%")
353 | export("%9>>5%")
354 | export("%10>>5%")
355 | export("%1>>6%")
356 | export("%2>>6%")
357 | export("%3>>6%")
358 | export("%4>>6%")
359 | export("%5>>6%")
360 | export("%6>>6%")
361 | export("%7>>6%")
362 | export("%8>>6%")
363 | export("%9>>6%")
364 | export("%10>>6%")
365 | export("%1>>7%")
366 | export("%2>>7%")
367 | export("%3>>7%")
368 | export("%4>>7%")
369 | export("%5>>7%")
370 | export("%6>>7%")
371 | export("%7>>7%")
372 | export("%8>>7%")
373 | export("%9>>7%")
374 | export("%10>>7%")
375 | export("%1>>8%")
376 | export("%2>>8%")
377 | export("%3>>8%")
378 | export("%4>>8%")
379 | export("%5>>8%")
380 | export("%6>>8%")
381 | export("%7>>8%")
382 | export("%8>>8%")
383 | export("%9>>8%")
384 | export("%10>>8%")
385 | export("%1>>9%")
386 | export("%2>>9%")
387 | export("%3>>9%")
388 | export("%4>>9%")
389 | export("%5>>9%")
390 | export("%6>>9%")
391 | export("%7>>9%")
392 | export("%8>>9%")
393 | export("%9>>9%")
394 | export("%10>>9%")
395 | export("%1>>10%")
396 | export("%2>>10%")
397 | export("%3>>10%")
398 | export("%4>>10%")
399 | export("%5>>10%")
400 | export("%6>>10%")
401 | export("%7>>10%")
402 | export("%8>>10%")
403 | export("%9>>10%")
404 | export("%10>>10%")
405 | export("%:>:%")
406 | export("%:>>:%")
407 | export("%:c>:%")
408 | export("%:i:%")
409 | export("%:pi:%")
410 | export("%>1%")
411 | export("%>2%")
412 | export("%>3%")
413 | export("%>4%")
414 | export("%>5%")
415 | export("%>6%")
416 | export("%>7%")
417 | export("%>8%")
418 | export("%>9%")
419 | export("%>10%")
420 | export("%>>1%")
421 | export("%>>2%")
422 | export("%>>3%")
423 | export("%>>4%")
424 | export("%>>5%")
425 | export("%>>6%")
426 | export("%>>7%")
427 | export("%>>8%")
428 | export("%>>9%")
429 | export("%>>10%")
430 | export(ModStore)
431 | export(Store)
432 | export(TidyModule)
433 | export(add_module)
434 | export(add_tm_snippets)
435 | export(callModules)
436 | export(check_and_load)
437 | export(combine_ports)
438 | export(defineEdges)
439 | export(getCacheOption)
440 | export(getMod)
441 | export(getSessionId)
442 | export(iport)
443 | export(listModules)
444 | export(map_ports)
445 | export(mod)
446 | export(oport)
447 | export(port)
448 | export(race_ports)
449 | export(session_type)
450 | export(showExamples)
451 | import(R6)
452 | import(dplyr)
453 | import(shiny)
454 | import(snippr)
455 | importFrom(cli,cat_boxx)
456 | importFrom(cli,cat_bullet)
457 | importFrom(fs,dir_create)
458 | importFrom(fs,dir_exists)
459 | importFrom(fs,file_create)
460 | importFrom(fs,file_exists)
461 | importFrom(fs,path)
462 | importFrom(fs,path_abs)
463 | importFrom(fs,path_ext_set)
464 | importFrom(fs,path_file)
465 | importFrom(fs,path_home_r)
466 | importFrom(methods,is)
467 | importFrom(purrr,discard)
468 | importFrom(purrr,keep)
469 | importFrom(purrr,map)
470 | importFrom(snippr,snippets_read)
471 | importFrom(utils,capture.output)
472 | importFrom(utils,file.edit)
473 | importFrom(utils,menu)
474 |
--------------------------------------------------------------------------------
/R/ModStore.R:
--------------------------------------------------------------------------------
1 |
2 | #' R6 Class Representing a ModStore
3 | #'
4 | #' @description
5 | #' This class is used to create a storage for tidymodules objects.
6 | #'
7 | #' @details
8 | #' Manage applications, sessions and modules.
9 | #'
10 | #' @import shiny
11 | #'
12 | #' @export
13 | ModStore <- R6::R6Class(
14 | "ModStore",
15 | public = list(
16 | #' @description
17 | #' Create a new ModStore object.
18 | #' Should be called once by the TidyModule class.
19 | #' Not to be called directly outside TidyModule.
20 | #' The ModStore object can be retrieved from any TidyModule object, see example below.
21 | #' @examples
22 | #' MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule)
23 | #' m <- MyModule$new()
24 | #' s <- m$getStore()
25 | #' @return A new `ModStore` object.
26 | initialize = function() {},
27 | #' @description
28 | #' Check if a module is stored in the current session.
29 | #' @param m TidyModule object.
30 | #' @examples
31 | #' MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule)
32 | #' m <- MyModule$new()
33 | #' s <- m$getStore()
34 | #' s$isStored(m)
35 | isStored = function(m) {
36 | s <- self$getSession(m)
37 | mod <- isolate(s$collection[[m$module_ns]])
38 | if (is.null(mod)) {
39 | return(FALSE)
40 | } else {
41 | return(TRUE)
42 | }
43 | },
44 | #' @description
45 | #' Retrieve the global session 'global_session'.
46 | #' This is the session that exists outside the application server function
47 | getGlobalSession = function() {
48 | sid <- "global_session"
49 | self$getSession(sid)
50 | },
51 | #' @description
52 | #' Retrieve a module session.
53 | #' This could be the global session or a user session.
54 | #' @param m TidyModule object.
55 | getSession = function(m) {
56 | isolate({
57 | return(private$getS(m))
58 | })
59 | },
60 | #' @description
61 | #' Retrieve all sessions.
62 | getSessions = function() {
63 | return(private$sessions)
64 | },
65 | #' @description
66 | #' Retrieve all modules.
67 | #' @param m TidyModule object.
68 | getMods = function(m) {
69 | s <- self$getSession(m)
70 | return(s$collection)
71 | },
72 | #' @description
73 | #' Retrieve modules connections.
74 | #' @param m TidyModule object.
75 | getEdges = function(m) {
76 | s <- self$getSession(m)
77 | return(s$edges)
78 | },
79 | #' @description
80 | #' Add modules connections into ModStore.
81 | #' An edge is either a connection between a reactive object and a module
82 | #' or between two modules.
83 | #' @param from list with three elements: m -> module, type -> input or output, port -> port Id.
84 | #' @param to list with three elements: m -> module, type -> input or output, port -> port Id.
85 | #' @param mode The type of edge, default to 'direct'.
86 | #' @param comment Any additional comment.
87 | addEdge = function(from,
88 | to,
89 | mode = "direct",
90 | comment = NA) {
91 | fromId <- fname <- fport <- ftype <- fclass <- NA
92 | toId <- tname <- tport <- ttype <- tclass <- NA
93 | s <- e <- d <- NULL
94 |
95 | isolate({
96 | if (is(to$m, "TidyModule")) {
97 | s <- to$m$getSession()
98 | e <- self$getEdges(to$m)
99 |
100 | toId <- to$m$module_ns
101 | tport <- to$port
102 | tname <- to$m$getPortName(to$port, to$type)
103 | ttype <- to$type
104 | tclass <- "TidyModule"
105 | }
106 |
107 | if (is(from$m, "TidyModule")) {
108 | if (is.null(s)) {
109 | s <- from$m$getSession()
110 | e <- self$getEdges(from$m)
111 | }
112 |
113 | fromId <- from$m$module_ns
114 | fport <- from$port
115 | fname <- from$m$getPortName(from$port, from$type)
116 | ftype <- from$type
117 | fclass <- "TidyModule"
118 |
119 | # Handle tidymodules derived ports
120 | } else if (!is.null(attr(from$m, "tidymodules")) &&
121 | attr(from$m, "tidymodules")) {
122 | mod <- attr(from$m, "tidymodules_operation")
123 | if (!is.null(mod) && mod == "combine") {
124 | mode <- mod
125 | combinedPorts <- reactiveValuesToList(from$m)
126 | for (key in names(combinedPorts)) {
127 | f <- combinedPorts[[key]]
128 | comment <- key
129 | fromId <- attr(f, "tidymodules_module_ns")
130 | fport <- attr(f, "tidymodules_port_id")
131 | ftype <- attr(f, "tidymodules_port_type")
132 | fname <- attr(f, "tidymodules_port_name")
133 | fclass <- "TidyModule"
134 |
135 | comb_row <- data.frame(
136 | from = fromId,
137 | fclass = fclass,
138 | fport = fport,
139 | ftype = ftype,
140 | fname = fname,
141 | to = toId,
142 | tclass = tclass,
143 | tport = tport,
144 | ttype = ttype,
145 | tname = tname,
146 | mode = mode,
147 | comment = comment
148 | )
149 |
150 | if (is.null(d)) {
151 | d <- comb_row
152 | } else {
153 | d <- rbind(d, comb_row)
154 | }
155 | }
156 | } else {
157 | fromId <- attr(from$m, "tidymodules_module_ns")
158 | fport <- attr(from$m, "tidymodules_port_id")
159 | ftype <- attr(from$m, "tidymodules_port_type")
160 | fname <- attr(from$m, "tidymodules_port_name")
161 | fclass <- "TidyModule"
162 | }
163 | } else if (is.reactive(from$m)) {
164 | fromId <- attr(from$m, "observable")$.reactId
165 | comment <- attr(from$m, "observable")$.label
166 | # support for previous shiny version that don't have reactId (don't work with shiny 1.0.5)
167 | if (is.null(fromId)) {
168 | fromId <- comment
169 | }
170 | fclass <- "reactive"
171 | } else {
172 | stop("Unknown 'from' entity in addEdge function ", class(from$m), "/n")
173 | }
174 |
175 | if (is.null(d)) {
176 | d <- data.frame(
177 | from = fromId,
178 | fclass = fclass,
179 | fport = fport,
180 | ftype = ftype,
181 | fname = fname,
182 | to = toId,
183 | tclass = tclass,
184 | tport = tport,
185 | ttype = ttype,
186 | tname = tname,
187 | mode = mode,
188 | comment = comment,
189 | stringsAsFactors = FALSE
190 | )
191 | }
192 |
193 | if (is.null(s) || s$sid == "global_session") {
194 | stop("addEdge function error! Module has no session or session is global [", s$sid, "]")
195 | }
196 |
197 | # track update time
198 | s$updated <- Sys.time()
199 |
200 | if (length(s$edges) == 0) {
201 | s$edges <- d
202 | } else {
203 | key <- paste0(as.character(d[1, ]), collapse = "|")
204 | keys <- apply(e, 1, paste0, collapse = "|")
205 | if (key %in% keys) {
206 | warning(paste0("Module mapping already exist!\n", key))
207 | } else {
208 | s$edges <- rbind(e, d)
209 | }
210 | }
211 | })
212 | },
213 | #' @description
214 | #' Remove module edges
215 | #' @param m TidyModule object.
216 | delEdges = function(m){
217 | isolate({
218 | s <- private$getS(m)
219 | ns <- as.character(m$module_ns)
220 | if (length(s$edges) != 0) {
221 | s$edges <- s$edges %>% filter(from != ns & to != ns)
222 | }
223 | })
224 | },
225 | #' @description
226 | #' Add module into the ModStore.
227 | #' @param m TidyModule object.
228 | addMod = function(m) {
229 | isolate({
230 | s <- private$getS(m)
231 | ns <- as.character(m$module_ns)
232 |
233 | # if(!is.null(s$collection[[ns]]))
234 | # stop(paste0("Module namespace ",ns," already stored!"))
235 | s$collection[[ns]] <- m
236 | if (!is.null(m$group)) {
237 | g <- as.character(m$group)
238 | if (is.null(s$g_collection[[g]])) {
239 | s$g_collection[[g]] <- list()
240 | }
241 | s$g_collection[[g]][[ns]] <- m
242 | }
243 | if (!is.null(m$parent_ns)) {
244 | p <- as.character(m$parent_ns)
245 | if (is.null(s$n_collection[[p]])) {
246 | s$n_collection[[p]] <- list()
247 | }
248 | s$n_collection[[p]][[ns]] <- m
249 | }
250 | # track update time
251 | s$updated <- Sys.time()
252 | # TODO : Do we really need this line below ?
253 | s$ns <- c(s$ns, as.character(m$module_ns))
254 | })
255 | },
256 | #' @description
257 | #' Delete a module from the ModStore.
258 | #' @param m TidyModule object.
259 | delMod = function(m) {
260 | isolate({
261 | s <- private$getS(m)
262 | ns <- as.character(m$module_ns)
263 | s$collection[[ns]] <- NULL
264 | if (!is.null(m$group)) {
265 | g <- as.character(m$group)
266 | if (!is.null(s$g_collection[[g]]))
267 | s$g_collection[[g]][[ns]] <- NULL
268 | }
269 | if (!is.null(m$parent_ns)) {
270 | p <- as.character(m$parent_ns)
271 | if (!is.null(s$n_collection[[p]]))
272 | s$n_collection[[p]][[ns]] <- NULL
273 | }
274 | s$ns <- s$ns[-grep(as.character(m$module_ns),s$ns)]
275 | # delete edges
276 | self$delEdges(m)
277 | # track update time
278 | s$updated <- Sys.time()
279 | })
280 | },
281 | #' @description
282 | #' Print the ModStore object.
283 | print = function() {
284 | aid <- private$getAID()
285 | isolate({
286 | str(private$sessions[[aid]]$global_session$collection)
287 | })
288 | }
289 | ),
290 | private = list(
291 | sessions = reactiveValues(),
292 | sessionExist = function(sid) {
293 | aid <- private$getAID()
294 | return(
295 | !is.null(private$sessions[[aid]]) &&
296 | !is.null(private$sessions[[aid]][[sid]])
297 | )
298 | },
299 | addSession = function(sid) {
300 | aid <- private$getAID()
301 | if (is.null(private$sessions[[aid]])) {
302 | private$sessions[[aid]] <- reactiveValues()
303 | }
304 |
305 | if (is.null(private$sessions[[aid]][[sid]])) {
306 | private$sessions[[aid]][[sid]] <- reactiveValues(
307 | aid = aid,
308 | path = getwd(),
309 | sid = sid,
310 | count = 0,
311 | created = Sys.time(),
312 | updated = Sys.time(),
313 | collection = list(),
314 | ns = c(),
315 | edges = data.frame()
316 | )
317 | } else {
318 | FALSE
319 | }
320 | },
321 | getS = function(m) {
322 | sid <- m
323 | if (is(m, "TidyModule")) {
324 | sid <- m$getSessionId()
325 | }
326 | aid <- private$getAID()
327 | if (!private$sessionExist(sid)) {
328 | private$addSession(sid)
329 | }
330 | return(private$sessions[[aid]][[sid]])
331 | },
332 | getAID = function() {
333 | return(digest::digest(getwd(), algo = "md5"))
334 | }
335 | )
336 | )
337 |
--------------------------------------------------------------------------------
/R/utility.R:
--------------------------------------------------------------------------------
1 |
2 | #'
3 | #' @title Retrieve module from ModStore
4 | #'
5 | #' @description This utility function retrieve tidymodules from the central ModStore
6 | #' using module namespace/id and/or group
7 | #'
8 | #' @param id Name or Id of the module
9 | #' @param group Group name
10 | #'
11 | #' @import shiny
12 | #'
13 | #' @export
14 | #'
15 | #' @examples
16 | #'
17 | #' MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule)
18 | #' MyModule$new("MyFirst")
19 | #' MyModule$new("MySecond")
20 | #' MyModule$new("MyThird", group = "B")
21 | #'
22 | #' # MyFirst
23 | #' getMod(1)
24 | #' getMod("MyFirst")
25 | #'
26 | #' # MySecond
27 | #' getMod(2)
28 | #'
29 | #' # MyThird
30 | #' getMod(2)
31 | #' getMod("B-MyThird")
32 | #' getMod(1, group = "B")
33 | #'
34 | getMod <- function(id = 1, group = NULL) {
35 | m <- UtilityModule$new()
36 | mod <- NULL
37 | c <- isolate(m$getSession()$collection)
38 | gc <- isolate(m$getSession()$g_collection)
39 |
40 | if (!is.null(group) && !is.numeric(id)) {
41 | id <- paste0(id, "-G-", group)
42 | }
43 |
44 | if (is.null(group)) {
45 | mod <- c[[id]]
46 | } else {
47 | mod <- gc[[group]][[id]]
48 | }
49 |
50 | if (is.null(mod)) {
51 | warning(paste0("Module ", id, " not found!"))
52 | }
53 |
54 | mod
55 | }
56 | #'
57 | #' @title Alias to getMod
58 | #'
59 | #' @description See \code{\link{getMod}}
60 | #'
61 | #' @param id Name or Id of the module
62 | #' @param group Group name
63 | #'
64 | #' @import shiny
65 | #'
66 | #' @export
67 | mod <- function(id = 1, group = NULL) {
68 | getMod(id, group)
69 | }
70 |
71 | #'
72 | #' @title Retrieve module's port
73 | #'
74 | #' @description This utility function retrieve the tidymodules port specified in the arguments.
75 | #'
76 | #' @param id Name or Id of the module
77 | #' @param g Module group name
78 | #' @param t Port type, in or out
79 | #' @param p Port Id or name
80 | #'
81 | #' @import shiny
82 | #'
83 | #' @export
84 | port <- function(id = 1, p = 1, t = "in", g = NULL) {
85 | m <- getMod(id, g)
86 | if (is.null(m)) {
87 | return(NULL)
88 | } else {
89 | if (t == "in") {
90 | return(m$getInputPort(p))
91 | } else {
92 | return(m$getOutputPort(p))
93 | }
94 | }
95 | }
96 | #'
97 | #' @title Retrieve input module's port
98 | #'
99 | #' @description This utility function retrieve the tidymodules input port specified in the arguments.
100 | #'
101 | #' @param id Name or Id of the module
102 | #' @param g Module group name
103 | #' @param p Port Id or name
104 | #'
105 | #' @import shiny
106 | #'
107 | #' @export
108 | iport <- function(id = 1, p = 1, g = NULL) {
109 | port(id, p, "in", g)
110 | }
111 | #'
112 | #' @title Retrieve output module's port
113 | #'
114 | #' @description This utility function retrieve the tidymodules output port specified in the arguments.
115 | #'
116 | #' @param id Name or Id of the module
117 | #' @param g Module group name
118 | #' @param p Port Id or name
119 | #'
120 | #' @import shiny
121 | #'
122 | #' @export
123 | oport <- function(id = 1, p = 1, g = NULL) {
124 | port(id, p, "out", g)
125 | }
126 |
127 | #'
128 | #' @title List modules in current session
129 | #'
130 | #' @description This function list module objects found in the current session
131 | #'
132 | #' @param verbose Display module description as well
133 | #' @param global use the global session? Default to FALSE
134 | #'
135 | #' @importFrom cli cat_bullet cat_boxx
136 | #' @importFrom utils capture.output
137 | #' @import shiny
138 | #'
139 | #' @export
140 | listModules <- function(verbose = FALSE, global = FALSE) {
141 | currentSession <- UtilityModule$new()$getSession()
142 | if(global)
143 | currentSession <- UtilityModule$new()$getGlobalSession()
144 | isolate({
145 | if (length(currentSession$collection) == 0) {
146 | cat_bullet(paste0("No module found!"),
147 | bullet_col = "orange",
148 | bullet = "cross"
149 | )
150 | } else {
151 | cat_bullet(paste0("Found ", length(currentSession$collection), " module(s)!"),
152 | bullet_col = "green",
153 | bullet = "tick"
154 | )
155 | }
156 | invisible(for (mod in currentSession$collection) {
157 | cat_bullet(mod$module_ns, bullet = "circle_dotted")
158 | if (verbose) {
159 | cat_boxx(capture.output(mod))
160 | }
161 | })
162 | })
163 | }
164 |
165 | #'
166 | #' @title Call modules function
167 | #'
168 | #' @description This utility function call all modules initialized in the global session.
169 | #' The global session is the session shared outside the server function of the application.
170 | #' All the modules initialized in the global session can be called with this function in a single call.
171 | #' The function take care of cloning and attaching them to the current user session.
172 | #'
173 | #' Note that this function can only be called in the app server function at the moment.
174 | #' We are working on supporting callModules within module server function for invoking nested modules.
175 | #'
176 | #'
177 | #' @import shiny
178 | #'
179 | #' @export
180 | callModules <- function() {
181 | currentSession <- UtilityModule$new()$getSession()
182 | globalSession <- UtilityModule$new()$getGlobalSession()
183 | disable_cache <- getCacheOption()
184 |
185 | calls <- c()
186 |
187 | isolate({
188 | # re-initialize current session
189 | currentSession$edges <- data.frame()
190 | currentSession$count <- globalSession$count
191 |
192 | lapply(globalSession$collection, function(mod) {
193 | if (is.null(currentSession$collection[[mod$module_ns]]) || disable_cache) {
194 | ######## Try to capture server function arguments here ########
195 | serverEnv <- parent.frame(3)
196 | o <- i <- s <- NULL
197 | if (!is.null(serverEnv)) {
198 | if (!is.null(serverEnv$input) &&
199 | is(serverEnv$input, "reactivevalues")) {
200 | i <- serverEnv$input
201 | }
202 | if (!is.null(serverEnv$output) &&
203 | is(serverEnv$output, "shinyoutput")) {
204 | o <- serverEnv$output
205 | }
206 | if (!is.null(serverEnv$session) &&
207 | is(serverEnv$session, "ShinySession")) {
208 | s <- serverEnv$session
209 | }
210 | if (is.null(s)) {
211 | s <- getDefaultReactiveDomain()
212 | }
213 | }
214 | cloned <- mod$deepClone(o, i, s)
215 | }
216 | # Don't invoke nested modules as they will be invoked by parents
217 | # TODO : Change function to allow callModules within Module server (inject nested modules)
218 | if (is.null(currentSession$collection[[mod$module_ns]]$parent_ns)) {
219 | calls <<- c(calls, currentSession$collection[[mod$module_ns]])
220 | }
221 | })
222 | })
223 | lapply(calls, function(m) m$callModule())
224 | }
225 | #'
226 | #' @title Function wrapper for ports connection expression.
227 | #'
228 | #' @description Used in server functions to define how modules are connected to each other.
229 | #'
230 | #' @param x expression
231 | #'
232 | #' @import shiny
233 | #'
234 | #' @export
235 | defineEdges <- function(x) {
236 | observe({
237 | isolate(x)
238 | })
239 | }
240 |
241 |
242 | #'
243 | #' @title Retrieve cache option from the environment
244 | #'
245 | #' @description The cache option `tm_disable_cache` is a global options that enable or disable the use of existing modules from the current session.
246 | #' This option is `FALSE` by default and should be used in concordance with the `tm_session_type` global option. See \code{\link{session_type}} for a list of possible session type.
247 | #'
248 | #' @export
249 | getCacheOption <- function() {
250 | disable_cache <- getOption("tm_disable_cache")
251 | if (is.null(disable_cache)) {
252 | disable_cache <- FALSE
253 | }
254 | disable_cache <- as.logical(disable_cache)
255 |
256 | if (is.na(disable_cache)) {
257 | stop("Option 'tm_disable_cache' should be set to a logical value or unset.")
258 | }
259 |
260 | disable_cache
261 | }
262 | #'
263 | #' @title List of possible session types
264 | #'
265 | #' @description tidymodules offers the ability to manage application sessions.
266 | #' At the moment the three options below are available.
267 | #'
268 | #' \itemize{
269 | #'
270 | #' \item{SHINY}{ : The default behaviour of shiny application and the default for tidymodules. Every time you access an application
271 | #' you get a new token Id that defines your application user session.}
272 | #'
273 | #' \item{USER}{ : This method defines a session based on the information available in the request object of shiny output.
274 | #' It is a concatenation of the variables REMOTE_ADDR, HTTP_HOST and PATH_INFO like below.
275 | #'
276 | #' \code{sid <- paste0(r$REMOTE_ADDR,"@",r$HTTP_HOST,r$PATH_INFO))}
277 | #'
278 | #' Note that the method is actually not working properly for now as the information available via the request object
279 | #' are not reflecting the actual user. We are working on a better method to uniquely identify a remote user.}
280 | #'
281 | #' \item{CUSTOM}{ : This method allow the developper to provide a custom function for generating the session Id.
282 | #' It relies on the global options `tm_session_custom` being set and pointing to a function taking a shiny output as argument.}
283 | #'
284 | #' }
285 | #'
286 | #' @export
287 | session_type <- list(
288 | SHINY = 1,
289 | USER = 2,
290 | CUSTOM = 3
291 | )
292 |
293 | #'
294 | #' @title tidymodules options
295 | #'
296 | #' @name global_options
297 | #'
298 | #' @description List of global options used to adjust tidymodules configuration.
299 | #'
300 | #' \itemize{
301 | #' \item{**tm_session_type**}{ : Define the type of the session, See available session types in \code{\link{session_type}} }
302 | #' \item{**tm_session_custom**}{ : Used to set a custom function for generating the session Id. Used in concordance with the `CUSTOM` session type.}
303 | #' \item{**tm_disable_cache**}{ : Disable caching of modules. This option is set to FALSE by default but is only relevant when user's session is managed properly. See also \code{\link{getCacheOption}}}
304 | #' }
305 | #'
306 | #' @rdname global_options
307 | #'
308 | NULL
309 |
310 | #'
311 | #' @title Function that generates session Id
312 | #'
313 | #' @description tidymodules offers the ability to manage application sessions.
314 | #' This function is the main function used by tidymodules to find the current session Id.
315 | #' It takes an optional ShinySession object as argument. If null, default to the global_session.
316 | #'
317 | #' @param session A shiny session as provide by the shiny server function.
318 | #'
319 | #' @return A session ID
320 | #'
321 | #' @import shiny
322 | #'
323 | #' @export
324 | getSessionId <- function(session = getDefaultReactiveDomain()) {
325 | if (is.null(session)) {
326 | return("global_session")
327 | } else {
328 | stype <- getOption("tm_session_type")
329 | sid <- NULL
330 | if (is.null(stype)) {
331 | stype <- session_type$SHINY
332 | }
333 | switch(stype,
334 | # SHINY
335 | {
336 | sid <- session$token
337 | },
338 | # USER
339 | {
340 | r <- session$request
341 | sid <- paste0(r$REMOTE_ADDR, "@", r$HTTP_HOST, r$PATH_INFO)
342 | },
343 | # CUSTOM
344 | {
345 | fct <- getOption("tm_session_custom")
346 | if (is.null(fct) || class(fct) != "function") {
347 | stop("Option 'tm_session_custom' should be set to a function taking a ShinySession object as option and generating a custom session ID used by tidymodules to identify module sessions.")
348 | }
349 | sid <- fct(session)
350 | }
351 | )
352 | return(sid)
353 | }
354 | }
355 |
356 |
357 | #'
358 | #' @title Recursive function for retrieving R6ClassGenerator inheritance
359 | #'
360 | #' @description This function is used to retrieve a list of class name that a R6ClassGenerator object inherit from.
361 | #'
362 | #' @param r6cg A R6ClassGenerator object.
363 | #'
364 | #' @return vector of class names
365 | #'
366 | #' @keywords internal
367 | get_R6CG_list <- function(r6cg) {
368 | if (!is(r6cg, "R6ClassGenerator")) {
369 | stop("provide a R6ClassGenerator object!")
370 | }
371 | clist <- r6cg$classname
372 | if (!is.null(r6cg$get_inherit())) {
373 | clist <- c(clist, get_R6CG_list(r6cg$get_inherit()))
374 | }
375 |
376 | return(clist)
377 | }
378 |
--------------------------------------------------------------------------------