brochure
                                
                                
                                
                                    brochure copied to clipboard
                            
                            
                            
                        Allow a form of /page/:id: à la express
Something that would allow to write :
ui <- function(request){
  brochure(
    page(
      href = "/profile/:id:",
      ui = tagList(
        h1("This is my first page"),
        h2(paste("Hello", {:id:}) 
      )
    )
  )
}
                                    
                                    
                                    
                                
Idea:
- Add a page as 
/this/{id}/ - is turned in the name to 
/this/([^/])/ - is extracted by handlers and UI and server
 
How does express.JS does that under the hood? I feel like I'm missing something.
Basically the idea is to define profile/{id} inside the page() function, then have profile/colin and profile/matilda both root to that page, and the id is available to the ui & server.
Hey, Colin! Great work here with this module.
On this issue, let me know what you think about the direction below.
This first part would be called once, as part of a "compilation" step:
compile_route <- function(route) {
  stopifnot(is.character(route) && length(route) == 1)
  params <- stringr::str_match_all(route, "\\{(\\w+)\\}")[[1]][,2]
  extractor <- stringr::str_replace_all(route, "\\{\\w+\\}", "([^//]+)")[[1]]
  list(
    template = route,
    parse = function(path) {
      parsed <- stringr::str_match_all(path, extractor)[[1]]
      if (!nrow(parsed)) return(NULL)
      setNames(parsed[1,-1], params)
    }
  )
}
route <- compile_route("/prefix/{arg1}/{arg2}-argsuffix/suffix")
And then in request time:
# NULL
route$parse("/blabla")
route$parse("/prefix")
route$parse("/prefix/1")
route$parse("/prefix/1/2-argsuffix")
route$parse("/prefix/1/almost/there-argsuffix/suffix")
# named vectors
route$parse("/prefix/1/2-argsuffix/suffix")
route$parse("/prefix/first/second with spaces-argsuffix/suffix")
Some timings, just for reference (8GB RAM, 2.7 GHz i7 CPU):
Unit: milliseconds
                                                             expr     min        lq
 route$parse("/prefix/first/second with spaces-argsuffix/suffix") 0.01958 0.0199325
        mean    median        uq      max neval
 0.020986522 0.0200675 0.0202895 0.117091  1000
Maybe this is missing some more validation in order to become user facing. I didn't test more weird inputs
Hey @abelborges,
Thanks a lot for this! Awesome.
I love the approach of returning a list with a function and all, smart. Let me take a closer look into that and see how it could work inside brochure 🤔
Draft :
Handler <- R6::R6Class(
  "Handler",
  public = list(
    extractor = NULL,
    params = NULL,
    initialize = function(href) {
      if (!grepl(".*/$", href)) {
        href <- sprintf("%s/", href)
      }
      self$params <- stringr::str_match_all(
        href,
        "\\{(\\w+)\\}"
      )[[1]][, 2]
      self$extractor <- stringr::str_replace_all(
        href,
        "\\{\\w+\\}",
        "([^//]+)"
      )[[1]]
      self$extractor <- sprintf(
        "^%s$",
        self$extractor
      )
    },
    matches = function(route) {
      if (!grepl(".*/$", route)) {
        route <- sprintf("%s/", route)
      }
      parsed <- stringr::str_match_all(
        route,
        self$extractor
      )[[1]]
      if (is_match <- as.logical(nrow(parsed))) {
        args <- as.list(setNames(parsed[1, -1], self$params))
      } else {
        args <- NULL
      }
      return(
        list(
          is_match = is_match,
          args = args
        )
      )
    }
  )
)
  pkgload::load_all()
  p <- Page$new(
    href = "/profile/{id}/",
    ui = tagList(),
    server = function(input, output, session) {}
  )
  res <- p$matches("/profile/colin")
  expect_true(
    res$is_match
  )
  expect_equal(
    res$args$id,
    "colin"
  )
  res <- p$matches("/profile/colin/true")
  expect_false(
    res$is_match
  )
  expect_equal(
    res$args,
    NULL
  )
                                    
                                    
                                    
                                
Ok back to this.
Internally, express uses https://www.npmjs.com/package/path-to-regexp, so maybe we should have something in the same spirit, that turns path into regex, then does a grepl on the route table 🤔