Browse Source

initial commit

master
boB Rudis 2 years ago
parent
commit
78ef594e40
No known key found for this signature in database GPG Key ID: 1D7529BE14E2BBA9
  1. 1
      .Rbuildignore
  2. 10
      DESCRIPTION
  3. 2
      LICENSE
  4. 21
      LICENSE.md
  5. 17
      NAMESPACE
  6. 1
      R/aaa.R
  7. 36
      R/all-q.R
  8. 7
      R/countenance-package.R
  9. 30
      R/forward.R
  10. 95
      R/main.R
  11. 42
      R/overtime-10.R
  12. 30
      R/pi-host.R
  13. 8
      R/pi-key.R
  14. 30
      R/query-types.R
  15. 20
      R/recent.R
  16. 20
      R/summary.R
  17. 33
      R/top-clients.R
  18. 43
      R/top-items.R
  19. 11
      R/utils-pipe.R
  20. 76
      README.md
  21. 5
      man/countenance.Rd
  22. 16
      man/pi_fwd_dest.Rd
  23. 17
      man/pi_host.Rd
  24. 14
      man/pi_key.Rd
  25. 16
      man/pi_over_time_10m.Rd
  26. 18
      man/pi_queries.Rd
  27. 16
      man/pi_query_types.Rd
  28. 16
      man/pi_recent.Rd
  29. 16
      man/pi_summary.Rd
  30. 28
      man/pi_test.Rd
  31. 18
      man/pi_top_clients.Rd
  32. 18
      man/pi_top_items.Rd
  33. 12
      man/pipe.Rd

1
.Rbuildignore

@ -19,3 +19,4 @@
^CRAN-RELEASE$
^appveyor\.yml$
^tools$
^LICENSE\.md$

10
DESCRIPTION

@ -1,6 +1,6 @@
Package: countenance
Type: Package
Title: countenance title goes here otherwise CRAN checks fail
Title: Tools to Work with the Pi-Hole API
Version: 0.1.0
Date: 2020-04-22
Authors@R: c(
@ -8,17 +8,19 @@ Authors@R: c(
comment = c(ORCID = "0000-0001-5670-2640"))
)
Maintainer: Bob Rudis <bob@rud.is>
Description: A good description goes here otherwise CRAN checks fail.
Description: Named after a primary synonym for 'pihole', tools are provided
to access the Pi-Hole API.
URL: https://git.rud.is/hrbrmstr/countenance
BugReports: https://gitlab.com/hrbrmstr/countenance/issues
Encoding: UTF-8
License: AGPL
License: MIT + file LICENSE
Suggests:
covr, tinytest
Depends:
R (>= 3.2.0)
Imports:
httr,
jsonlite
jsonlite,
magrittr
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.1.0

2
LICENSE

@ -0,0 +1,2 @@
YEAR: 2020
COPYRIGHT HOLDER: Bob Rudis

21
LICENSE.md

@ -0,0 +1,21 @@
# MIT License
Copyright (c) 2020 Bob Rudis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

17
NAMESPACE

@ -1,4 +1,21 @@
# Generated by roxygen2: do not edit by hand
export("%>%")
export(pi_disable)
export(pi_enable)
export(pi_fwd_dest)
export(pi_host)
export(pi_key)
export(pi_over_time_10m)
export(pi_queries)
export(pi_query_types)
export(pi_recent)
export(pi_summary)
export(pi_test)
export(pi_top_clients)
export(pi_top_items)
export(pi_type)
export(pi_version)
import(httr)
importFrom(jsonlite,fromJSON)
importFrom(magrittr,"%>%")

1
R/aaa.R

@ -0,0 +1 @@
set_names <- function (object = nm, nm) { names(object) <- nm ; object }

36
R/all-q.R

@ -0,0 +1,36 @@
#' Get query types distribution
#'
#' @param q query
#' @param host See [pi_host()]
#' @param api_key See [pi_key()]
#' @export
pi_queries<- function(q = NULL, host = pi_host(), api_key = pi_key()) {
pi_hole_api_url <- paste0("http://", host, "/admin/api.php")
httr::GET(
url = paste0(pi_hole_api_url, "?", "getAllQueries", "&", "auth", "=", api_key)
) -> res
httr::stop_for_status(res)
out <- httr::content(res, as = "text", encoding = "UTF-8")
out <- jsonlite::fromJSON(out)
out <- out[["data"]]
out <- as.data.frame(out, stringsAsFactors = FALSE)
set_names(
out,
c("ts", "query_type", "domain", "client", "answer_type", "X1", "X2", "X3")
) -> out
out$ts <- .POSIXct(as.numeric(out$ts))
class(out) <- c("tbl_df", "tbl", "data.frame")
out
}

7
R/countenance-package.R

@ -1,5 +1,8 @@
#' ...
#'
#' Tools to Work with the Pi-Hole API
#'
#' Named after a primary synonym for 'pihole', tools are provided
#' to access the Pi-Hole API.
#'
#' @md
#' @name countenance
#' @keywords internal

30
R/forward.R

@ -0,0 +1,30 @@
#' Get forward destinations (i.e. upstream DNS)
#'
#' @param host See [pi_host()]
#' @param api_key See [pi_key()]
#' @export
pi_fwd_dest <- function(host = pi_host(), api_key = pi_key()) {
pi_hole_api_url <- paste0("http://", host, "/admin/api.php")
httr::GET(
url = paste0(pi_hole_api_url, "?", "getForwardDestinations", "&", "auth", "=", api_key)
) -> res
httr::stop_for_status(res)
out <- httr::content(res)
out <- out$forward_destinations
data.frame(
dest = names(out),
ct = unlist(unname(out)),
stringsAsFactors = FALSE
) -> out
class(out) <- c("tbl_df", "tbl", "data.frame")
out
}

95
R/main.R

@ -0,0 +1,95 @@
#' Test connectivity to/get info about a Pi-Hole
#'
#' @param host See [pi_host()]
#' @param api_key See [pi_key()]
#' @export
pi_test <- function(host = pi_host(), api_key = pi_key()) {
pi_hole_api_url <- paste0("http://", host, "/admin/api.php")
httr::GET(
url = pi_hole_api_url,
query = list(
auth = api_key
)
) -> res
httr::stop_for_status(res)
h <- httr::headers(res)
any(grepl("working", tolower(h[["x-pi-hole"]])))
}
#' @rdname pi_test
#' @export
pi_type <- function(host = pi_host(), api_key = pi_key()) {
pi_hole_api_url <- paste0("http://", host, "/admin/api.php")
httr::GET(
url = paste0(pi_hole_api_url, "?", "type")
) -> res
httr::stop_for_status(res)
out <- httr::content(res)
out$type
}
#' @rdname pi_test
#' @export
pi_version <- function(host = pi_host(), api_key = pi_key()) {
pi_hole_api_url <- paste0("http://", host, "/admin/api.php")
httr::GET(
url = paste0(pi_hole_api_url, "?", "version")
) -> res
httr::stop_for_status(res)
out <- httr::content(res)
out$version
}
#' @rdname pi_test
#' @export
pi_enable <- function(host = pi_host(), api_key = pi_key()) {
pi_hole_api_url <- paste0("http://", host, "/admin/api.php")
httr::GET(
url = paste0(pi_hole_api_url, "?", "enable")
) -> res
httr::stop_for_status(res)
out <- httr::content(res)
out$version
}
#' @rdname pi_test
#' @export
pi_disable <- function(host = pi_host(), api_key = pi_key()) {
pi_hole_api_url <- paste0("http://", host, "/admin/api.php")
httr::GET(
url = paste0(pi_hole_api_url, "?", "disable")
) -> res
httr::stop_for_status(res)
out <- httr::content(res)
out$version
}

42
R/overtime-10.R

@ -0,0 +1,42 @@
#' Get recent 10 min time series counts
#'
#' @param host See [pi_host()]
#' @param api_key See [pi_key()]
#' @export
pi_over_time_10m <- function(host = pi_host(), api_key = pi_key()) {
pi_hole_api_url <- paste0("http://", host, "/admin/api.php")
httr::GET(
url = paste0(pi_hole_api_url, "?", "overTimeData10mins", "&", "auth=", api_key)
) -> res
httr::stop_for_status(res)
out <- httr::content(res)
data.frame(
kind = "query",
ts = names(out$domains_over_time),
ct = unlist(unname(out$domains_over_time)),
stringsAsFactors = FALSE
) -> x1
x1$ts <- .POSIXct(as.numeric(x1$ts))
data.frame(
kind = "ad",
ts = names(out$ads_over_time),
ct = unlist(unname(out$ads_over_time)),
stringsAsFactors = FALSE
) -> x2
x2$ts <- .POSIXct(as.numeric(x2$ts))
out <- rbind.data.frame(x1, x2)
class(out) <- c("tbl_df", "tbl", "data.frame")
out
}

30
R/pi-host.R

@ -0,0 +1,30 @@
#' Get or set PIHOLE_HOST value
#'
#' @md
#' @param force Force setting a new Pi-Hole host[:port] the current environment?
#' @return atomic character vector containing the Pi-Hole host[:port]
#' @export
pi_host <- function(force = FALSE) {
env <- Sys.getenv('PIHOLE_HOST')
if (!identical(env, "") && !force) return(env)
if (!interactive()) {
stop("Please set env var PIHOLE_HOST to your Pi-Hole host[:port]",
call. = FALSE)
}
message("Couldn't find env var PIHOLE_HOST See ?pi_host for more details.")
message("Please enter your Pi-Hole host[:port]:")
pat <- readline(": ")
if (identical(pat, "")) {
stop("Pi-Hole host[:port] entry failed", call. = FALSE)
}
message("Updating PIHOLE_HOST env var")
Sys.setenv(PIHOLE_HOST = pat)
pat
}

8
R/pi-key.R

@ -0,0 +1,8 @@
#' Get PIHOLE_API_KEY value
#'
#' Can be non-existant or blank ("")
#'
#' @md
#' @return atomic character vector containing the Pi-Hole API key
#' @export
pi_key <- function() { Sys.getenv('PIHOLE_API_KEY') }

30
R/query-types.R

@ -0,0 +1,30 @@
#' Get query types distribution
#'
#' @param host See [pi_host()]
#' @param api_key See [pi_key()]
#' @export
pi_query_types <- function(host = pi_host(), api_key = pi_key()) {
pi_hole_api_url <- paste0("http://", host, "/admin/api.php")
httr::GET(
url = paste0(pi_hole_api_url, "?", "getQueryTypes", "&", "auth", "=", api_key)
) -> res
httr::stop_for_status(res)
out <- httr::content(res)
out <- out$querytypes
data.frame(
type = names(out),
pct = unlist(unname(out))/100,
stringsAsFactors = FALSE
) -> out
class(out) <- c("tbl_df", "tbl", "data.frame")
out
}

20
R/recent.R

@ -0,0 +1,20 @@
#' Get recent blocked
#'
#' @param host See [pi_host()]
#' @param api_key See [pi_key()]
#' @export
pi_recent <- function(host = pi_host(), api_key = pi_key()) {
pi_hole_api_url <- paste0("http://", host, "/admin/api.php")
httr::GET(
url = paste0(pi_hole_api_url, "?", "recentBlocked", "&", "auth=", api_key)
) -> res
httr::stop_for_status(res)
out <- httr::content(res, as = "text", encoding = "UTF-8")
out
}

20
R/summary.R

@ -0,0 +1,20 @@
#' Get summary stats
#'
#' @param host See [pi_host()]
#' @param api_key See [pi_key()]
#' @export
pi_summary <- function(host = pi_host(), api_key = pi_key()) {
pi_hole_api_url <- paste0("http://", host, "/admin/api.php")
httr::GET(
url = paste0(pi_hole_api_url, "?", "summaryRaw", "&", "auth=", api_key)
) -> res
httr::stop_for_status(res)
out <- httr::content(res)
out
}

33
R/top-clients.R

@ -0,0 +1,33 @@
#' Get top clients
#'
#' @param n \# of top clients (default 100)
#' @param host See [pi_host()]
#' @param api_key See [pi_key()]
#' @export
pi_top_clients <- function(n = 100, host = pi_host(), api_key = pi_key()) {
pi_hole_api_url <- paste0("http://", host, "/admin/api.php")
n <- as.integer(n[1])
httr::GET(
url = paste0(pi_hole_api_url, "?", "topClients", "=", n, "&", "auth", "=", api_key)
) -> res
httr::stop_for_status(res)
out <- httr::content(res)
out <- out$top_sources
data.frame(
client = names(out),
ct = unlist(unname(out)),
stringsAsFactors = FALSE
) -> out
class(out) <- c("tbl_df", "tbl", "data.frame")
out
}

43
R/top-items.R

@ -0,0 +1,43 @@
#' Get top items
#'
#' @param n \# of top items (default 100)
#' @param host See [pi_host()]
#' @param api_key See [pi_key()]
#' @export
pi_top_items <- function(n = 100L, host = pi_host(), api_key = pi_key()) {
pi_hole_api_url <- paste0("http://", host, "/admin/api.php")
n <- as.integer(n[1])
pi_hole_api_url <- paste0("http://", host, "/admin/api.php")
httr::GET(
url = paste0(pi_hole_api_url, "?", "topItems", "=", n, "&", "auth=", api_key)
) -> res
httr::stop_for_status(res)
out <- httr::content(res)
data.frame(
kind = "query",
domain = names(out$top_queries),
ct = unlist(unname(out$top_queries)),
stringsAsFactors = FALSE
) -> x1
data.frame(
kind = "ad",
domain = names(out$top_ads),
ct = unlist(unname(out$top_ads)),
stringsAsFactors = FALSE
) -> x2
out <- rbind.data.frame(x1, x2)
class(out) <- c("tbl_df", "tbl", "data.frame")
out
}

11
R/utils-pipe.R

@ -0,0 +1,11 @@
#' Pipe operator
#'
#' See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details.
#'
#' @name %>%
#' @rdname pipe
#' @keywords internal
#' @export
#' @importFrom magrittr %>%
#' @usage lhs \%>\% rhs
NULL

76
README.md

@ -0,0 +1,76 @@
[![Project Status: Active – The project has reached a stable, usable
state and is being actively
developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)
[![Signed
by](https://img.shields.io/badge/Keybase-Verified-brightgreen.svg)](https://keybase.io/hrbrmstr)
![Signed commit
%](https://img.shields.io/badge/Signed_Commits-100%25-lightgrey.svg)
[![Linux build
Status](https://travis-ci.org/hrbrmstr/countenance.svg?branch=master)](https://travis-ci.org/hrbrmstr/countenance)
![Minimal R
Version](https://img.shields.io/badge/R%3E%3D-3.2.0-blue.svg)
![License](https://img.shields.io/badge/License-MIT-blue.svg)
# countenance
Tools to Work with the Pi-Hole API
## Description
Named after a primary synonym for ‘pihole’, tools are provided to access
the Pi-Hole API.
## What’s Inside The Tin
The following functions are implemented:
- `pi_fwd_dest`: Get forward destinations (i.e. upstream DNS)
- `pi_host`: Get or set PIHOLE\_HOST value
- `pi_key`: Get PIHOLE\_API\_KEY value
- `pi_over_time_10m`: Get recent 10 min time series counts
- `pi_queries`: Get query types distribution
- `pi_query_types`: Get query types distribution
- `pi_recent`: Get recent blocked
- `pi_summary`: Get summary stats
- `pi_test`: Test connectivity to/get info about a Pi-Hole
- `pi_top_clients`: Get top clients
- `pi_top_items`: Get top items
## Installation
``` r
remotes::install_git("https://git.rud.is/hrbrmstr/countenance.git")
# or
remotes::install_git("https://git.sr.ht/~hrbrmstr/countenance")
# or
remotes::install_gitlab("hrbrmstr/countenance")
# or
remotes::install_github("hrbrmstr/countenance")
```
NOTE: To use the ‘remotes’ install options you will need to have the
[{remotes} package](https://github.com/r-lib/remotes) installed.
## Usage
``` r
library(countenance)
# current version
packageVersion("countenance")
## [1] '0.1.0'
```
## countenance Metrics
| Lang | \# Files | (%) | LoC | (%) | Blank lines | (%) | \# Lines | (%) |
| :--- | -------: | ---: | --: | ---: | ----------: | ---: | -------: | ---: |
| R | 15 | 0.94 | 207 | 0.96 | 119 | 0.89 | 90 | 0.76 |
| Rmd | 1 | 0.06 | 8 | 0.04 | 15 | 0.11 | 28 | 0.24 |
## Code of Conduct
Please note that this project is released with a Contributor Code of
Conduct. By participating in this project you agree to abide by its
terms.

5
man/countenance.Rd

@ -4,9 +4,10 @@
\name{countenance}
\alias{countenance}
\alias{countenance-package}
\title{...}
\title{Tools to Work with the Pi-Hole API}
\description{
A good description goes here otherwise CRAN checks fail.
Named after a primary synonym for 'pihole', tools are provided
to access the Pi-Hole API.
}
\seealso{
Useful links:

16
man/pi_fwd_dest.Rd

@ -0,0 +1,16 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/forward.R
\name{pi_fwd_dest}
\alias{pi_fwd_dest}
\title{Get forward destinations (i.e. upstream DNS)}
\usage{
pi_fwd_dest(host = pi_host(), api_key = pi_key())
}
\arguments{
\item{host}{See \code{\link[=pi_host]{pi_host()}}}
\item{api_key}{See \code{\link[=pi_key]{pi_key()}}}
}
\description{
Get forward destinations (i.e. upstream DNS)
}

17
man/pi_host.Rd

@ -0,0 +1,17 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/pi-host.R
\name{pi_host}
\alias{pi_host}
\title{Get or set PIHOLE_HOST value}
\usage{
pi_host(force = FALSE)
}
\arguments{
\item{force}{Force setting a new Pi-Hole host\link{:port} the current environment?}
}
\value{
atomic character vector containing the Pi-Hole host\link{:port}
}
\description{
Get or set PIHOLE_HOST value
}

14
man/pi_key.Rd

@ -0,0 +1,14 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/pi-key.R
\name{pi_key}
\alias{pi_key}
\title{Get PIHOLE_API_KEY value}
\usage{
pi_key()
}
\value{
atomic character vector containing the Pi-Hole API key
}
\description{
Can be non-existant or blank ("")
}

16
man/pi_over_time_10m.Rd

@ -0,0 +1,16 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/overtime-10.R
\name{pi_over_time_10m}
\alias{pi_over_time_10m}
\title{Get recent 10 min time series counts}
\usage{
pi_over_time_10m(host = pi_host(), api_key = pi_key())
}
\arguments{
\item{host}{See \code{\link[=pi_host]{pi_host()}}}
\item{api_key}{See \code{\link[=pi_key]{pi_key()}}}
}
\description{
Get recent 10 min time series counts
}

18
man/pi_queries.Rd

@ -0,0 +1,18 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/all-q.R
\name{pi_queries}
\alias{pi_queries}
\title{Get query types distribution}
\usage{
pi_queries(q = NULL, host = pi_host(), api_key = pi_key())
}
\arguments{
\item{q}{query}
\item{host}{See \code{\link[=pi_host]{pi_host()}}}
\item{api_key}{See \code{\link[=pi_key]{pi_key()}}}
}
\description{
Get query types distribution
}

16
man/pi_query_types.Rd

@ -0,0 +1,16 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/query-types.R
\name{pi_query_types}
\alias{pi_query_types}
\title{Get query types distribution}
\usage{
pi_query_types(host = pi_host(), api_key = pi_key())
}
\arguments{
\item{host}{See \code{\link[=pi_host]{pi_host()}}}
\item{api_key}{See \code{\link[=pi_key]{pi_key()}}}
}
\description{
Get query types distribution
}

16
man/pi_recent.Rd

@ -0,0 +1,16 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/recent.R
\name{pi_recent}
\alias{pi_recent}
\title{Get recent blocked}
\usage{
pi_recent(host = pi_host(), api_key = pi_key())
}
\arguments{
\item{host}{See \code{\link[=pi_host]{pi_host()}}}
\item{api_key}{See \code{\link[=pi_key]{pi_key()}}}
}
\description{
Get recent blocked
}

16
man/pi_summary.Rd

@ -0,0 +1,16 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/summary.R
\name{pi_summary}
\alias{pi_summary}
\title{Get summary stats}
\usage{
pi_summary(host = pi_host(), api_key = pi_key())
}
\arguments{
\item{host}{See \code{\link[=pi_host]{pi_host()}}}
\item{api_key}{See \code{\link[=pi_key]{pi_key()}}}
}
\description{
Get summary stats
}

28
man/pi_test.Rd

@ -0,0 +1,28 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/main.R
\name{pi_test}
\alias{pi_test}
\alias{pi_type}
\alias{pi_version}
\alias{pi_enable}
\alias{pi_disable}
\title{Test connectivity to/get info about a Pi-Hole}
\usage{
pi_test(host = pi_host(), api_key = pi_key())
pi_type(host = pi_host(), api_key = pi_key())
pi_version(host = pi_host(), api_key = pi_key())
pi_enable(host = pi_host(), api_key = pi_key())
pi_disable(host = pi_host(), api_key = pi_key())
}
\arguments{
\item{host}{See \code{\link[=pi_host]{pi_host()}}}
\item{api_key}{See \code{\link[=pi_key]{pi_key()}}}
}
\description{
Test connectivity to/get info about a Pi-Hole
}

18
man/pi_top_clients.Rd

@ -0,0 +1,18 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/top-clients.R
\name{pi_top_clients}
\alias{pi_top_clients}
\title{Get top clients}
\usage{
pi_top_clients(n = 100, host = pi_host(), api_key = pi_key())
}
\arguments{
\item{n}{\# of top clients (default 100)}
\item{host}{See \code{\link[=pi_host]{pi_host()}}}
\item{api_key}{See \code{\link[=pi_key]{pi_key()}}}
}
\description{
Get top clients
}

18
man/pi_top_items.Rd

@ -0,0 +1,18 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/top-items.R
\name{pi_top_items}
\alias{pi_top_items}
\title{Get top items}
\usage{
pi_top_items(n = 100L, host = pi_host(), api_key = pi_key())
}
\arguments{
\item{n}{\# of top items (default 100)}
\item{host}{See \code{\link[=pi_host]{pi_host()}}}
\item{api_key}{See \code{\link[=pi_key]{pi_key()}}}
}
\description{
Get top items
}

12
man/pipe.Rd

@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils-pipe.R
\name{\%>\%}
\alias{\%>\%}
\title{Pipe operator}
\usage{
lhs \%>\% rhs
}
\description{
See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details.
}
\keyword{internal}
Loading…
Cancel
Save