Skip to content

Commit 2ed99ce

Browse files
Merge pull request #1 from eoda-dev/rename-to-typewriter
Rename to typewriter
2 parents 287abdf + 18cb8c2 commit 2ed99ce

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1497
-949
lines changed

.Rbuildignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ _NAMESPACE
99
^docs$
1010
^pkgdown$
1111
_deprecated/
12+
^doc$
13+
^Meta$
14+
_obsolete/

.github/workflows/R-CMD-check.yaml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@ jobs:
2020
fail-fast: false
2121
matrix:
2222
config:
23-
- {os: macos-latest, r: 'release'}
23+
- {os: macos-latest, r: '4.4.1'}
2424
- {os: windows-latest, r: 'release'}
25-
- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
26-
- {os: ubuntu-latest, r: 'release'}
27-
- {os: ubuntu-latest, r: 'oldrel-1'}
25+
- {os: windows-latest, r: 'devel', http-user-agent: 'release'}
26+
# - {os: windows-latest, r: '3.6.1'}
27+
- {os: windows-latest, r: 'oldrel-1'}
28+
- {os: windows-latest, r: '4.2.1'}
29+
- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
30+
- {os: ubuntu-latest, r: 'release'}
31+
- {os: ubuntu-latest, r: 'oldrel-1'}
2832

2933
env:
3034
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
.Ruserdata
55
docs
66
inst/doc
7+
/doc/
8+
/Meta/

DESCRIPTION

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Package: rdantic
1+
Package: typewriter
22
Type: Package
33
Title: Type Safety for R
44
Version: 0.1.0
@@ -10,18 +10,18 @@ Authors@R: c(
1010
Maintainer: Stefan Kuethe <[email protected]>
1111
Description: Adds type safety to R.
1212
URL:
13-
https://github.com/eoda-dev/rdantic
14-
https://eoda-dev.github.io/rdantic/
15-
BugReports: https://github.com/eoda-dev/rdantic/issues
13+
https://github.com/eoda-dev/typewriter
14+
https://eoda-dev.github.io/typewriter/
15+
BugReports: https://github.com/eoda-dev/typewriter/issues
1616
License: MIT + file LICENSE
1717
Encoding: UTF-8
1818
LazyData: true
1919
Imports:
2020
glue,
21-
purrr,
2221
rlang
2322
RoxygenNote: 7.3.2
2423
Suggests:
24+
jsonlite,
2525
knitr,
2626
rmarkdown,
2727
testthat (>= 3.0.0)

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
YEAR: 2024
2-
COPYRIGHT HOLDER: rdantic authors
2+
COPYRIGHT HOLDER: typewriter authors

LICENSE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# MIT License
22

3-
Copyright (c) 2024 rdantic authors
3+
Copyright (c) 2024 typewriter authors
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

NAMESPACE

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
# Generated by roxygen2: do not edit by hand
22

3-
S3method("$<-",rdantic)
4-
S3method("[[<-",rdantic)
5-
S3method(print,rdantic)
3+
S3method("$<-",typewriter)
4+
S3method("[[<-",typewriter)
5+
S3method(print,typewriter)
6+
export(Optional)
7+
export(Union)
68
export(base_model)
7-
export(base_settings)
89
export(check_args)
10+
export(dtype)
911
export(is_any)
10-
export(is_rdantic_model)
12+
export(is_typewriter_model)
1113
export(model_config)
14+
export(model_dump)
1215
export(model_field)
16+
export(model_from_template)
1317
export(model_validate)
1418
export(names_to_camel_case)
1519
export(names_to_snake_case)
20+
export(typed_struct)
1621
importFrom(utils,capture.output)
22+
importFrom(utils,modifyList)
1723
importFrom(utils,str)

NEWS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
# rdantic 0.1.0
1+
# typewriter 0.1.0
22

33
* Add NEWS.md to track changes.

R/base-model.R

Lines changed: 90 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,32 @@ model_fields <- function(model_fn) {
77
#' Create a model field
88
#' @param fn A type check function.
99
#' @param default A default value for the field.
10-
#' @param alias,... **not used** at the moment
10+
#' @param alias alias that can be used in [model_dump()]
11+
#' @param error_msg,... **not used** at the moment
12+
#' @returns A model field.
1113
#' @export
12-
model_field <- function(fn, default = NA, alias = NULL, ...) {
13-
l <- as.list(environment())
14-
return(structure(c(l, list(...)), class = CLASS_RDANTIC_MODEL_FIELD))
14+
model_field <- function(fn, default = NA, alias = NULL, error_msg = NULL, ...) {
15+
# l <- as.list(environment())
16+
# return(structure(c(l, list(...)), class = CLASS_MODEL_FIELD))
17+
obj <- c(as.list(environment()), list(...))
18+
base_class <- class(obj)
19+
structure(obj, class = c(base_class, CLASS_MODEL_FIELD))
1520
}
1621

1722
# ---
1823
#' Create a model config object
19-
#' @param allow_extra Whether to allow extra fields without type check.
24+
#' @param extra Whether to allow extra fields without type check.
2025
#' @param str_to_lower Convert all strings to lower case.
2126
#' @param ... **not used** at the moment
2227
#' @returns A model config object that can be used in [base_model()].
28+
#' @example examples/api/model-config.R
2329
#' @export
24-
model_config <- function(allow_extra = FALSE,
30+
model_config <- function(extra = c("ignore", "allow", "forbid"),
2531
str_to_lower = FALSE, ...) {
26-
return(structure(c(as.list(environment()), list(...)), class = CLASS_MODEL_CONFIG))
32+
obj <- c(as.list(environment()), list(...))
33+
obj$extra <- match.arg(extra)
34+
base_class <- class(obj)
35+
return(structure(obj, class = c(base_class, CLASS_MODEL_CONFIG)))
2736
}
2837

2938
# ---
@@ -38,42 +47,58 @@ model_config <- function(allow_extra = FALSE,
3847
#' that are executed before the type checks.
3948
#' @param .validators_after A named list of field validators
4049
#' that are executed after the type checks.
50+
#' @param .strict_args_order If set to `TRUE`, the `.x` parameter
51+
#' of the returned model factory function will be the last function argument.
52+
#' This is useful if you want to pass the arguments unnamed.
4153
#' @returns A model factory function.
4254
#' @example examples/api/base-model.R
55+
#' @importFrom utils modifyList
4356
#' @export
4457
base_model <- function(fields = list(), ...,
4558
.model_config = model_config(),
4659
.model_pre_init = NULL,
4760
.model_post_init = NULL,
4861
.validators_before = list(),
49-
.validators_after = list()) {
50-
fields <- utils::modifyList(fields, list(...), keep.null = TRUE)
51-
fields <- purrr::map(fields, ~ {
52-
if (inherits(.x, c("function", "formula"))) {
53-
return(model_field(fn = .x))
54-
}
55-
56-
if (inherits(.x, CLASS_RDANTIC_MODEL)) {
62+
.validators_after = list(),
63+
.strict_args_order = FALSE) {
64+
fields <- modifyList(fields, list(...), keep.null = TRUE)
65+
fields <- Map(function(.x) {
66+
if (inherits(.x, CLASS_MODEL_FUNCTION)) {
5767
model_fn <- .x
5868
fn <- function(x) {
5969
is.list(model_validate(x, model_fn))
6070
}
6171
return(model_field(fn = fn))
6272
}
6373

74+
if (!inherits(.x, CLASS_MODEL_FIELD)) {
75+
.x <- model_field(fn = .x)
76+
}
77+
78+
if (is.character(.x$fn)) {
79+
.x$fn <- type_check_fn_from_str(.x$fn)
80+
}
81+
6482
return(.x)
65-
})
83+
}, fields)
6684

67-
model_args <- purrr::map(fields, ~ .x$default)
85+
# model_args <- purrr::map(fields, ~ .x$default)
86+
model_args <- Map(function(x) x$default, fields)
87+
fn_args <- c(alist(.x = NULL), model_args, alist(... = ))
88+
if (.strict_args_order) {
89+
fn_args <- c(model_args, alist(... = , .x = NULL))
90+
}
6891

92+
# ---
6993
# Create model factory function
70-
model_fn <- rlang::new_function(c(model_args, alist(... = , .x = NULL)), quote({
94+
model_fn <- rlang::new_function(fn_args, quote({
7195
if (is_not_null(.x)) {
7296
obj <- .x
7397
} else {
7498
obj <- c(as.list(environment()), list(...))
7599
}
76100

101+
caller_fn_name <- as.character(match.call()[[1]])
77102
errors <- list()
78103

79104
obj <- validate_fields(obj, .validators_before)
@@ -83,23 +108,23 @@ base_model <- function(fields = list(), ...,
83108
}
84109

85110
for (name in names(fields)) {
86-
check_type_fn <- rlang::as_function(fields[[name]]$fn)
111+
type_check_fn <- rlang::as_function(fields[[name]]$fn)
87112
obj_value <- obj[[name]]
88-
if (isFALSE(check_type_fn(obj_value))) {
113+
114+
if (!all(type_check_fn(obj_value))) {
89115
errors[[name]] <- list(
90116
name = name,
91117
value = obj_value,
92-
type = typeof(obj_value),
93-
len = length(obj_value),
94-
type_check_failed = check_type_fn
118+
type_check_fn = type_check_fn
95119
)
96120
}
97121
}
98122

99123
obj <- validate_fields(obj, .validators_after)
100124

101-
if (isTRUE(.model_config$str_to_lower)) {
102-
obj <- purrr::map_depth(obj, -1, str_to_lower)
125+
if (.model_config$str_to_lower) {
126+
# obj <- purrr::map_depth(obj, -1, str_to_lower)
127+
obj <- map_depth_base(obj, -1, str_to_lower)
103128
}
104129

105130
if (length(errors) > 0) {
@@ -111,8 +136,15 @@ base_model <- function(fields = list(), ...,
111136
return(invisible(obj))
112137
}
113138

114-
if (isFALSE(.model_config$allow_extra)) {
115-
obj <- purrr::keep_at(obj, names(fields))
139+
if (.model_config$extra == "ignore") {
140+
obj <- obj[names(fields)]
141+
}
142+
143+
if (.model_config$extra == "forbid") {
144+
extra_fields <- !names(obj) %in% names(fields)
145+
if (any(extra_fields)) {
146+
stop("Forbidden field(s): ", paste(names(obj)[extra_fields], collapse = ", "))
147+
}
116148
}
117149

118150
if (is_not_null(.model_post_init)) {
@@ -123,14 +155,14 @@ base_model <- function(fields = list(), ...,
123155
return(obj)
124156
}
125157

126-
return(structure(obj, fields = fields, class = c(class(obj), CLASS_RDANTIC)))
158+
return(structure(obj, fields = fields, class = c(class(obj), CLASS_MODEL, caller_fn_name)))
127159
}))
128160

129161
return(
130162
structure(
131163
model_fn,
132164
fields = fields,
133-
class = CLASS_RDANTIC_MODEL
165+
class = CLASS_MODEL_FUNCTION
134166
)
135167
)
136168
}
@@ -157,18 +189,18 @@ check_args <- function(...) {
157189
if (length(fields) == 0) {
158190
fn <- rlang::caller_fn()
159191
fmls <- rlang::fn_fmls(fn)
160-
fields <- purrr::map(as.list(fmls), eval)
192+
fields <- Map(eval, as.list(fmls))
161193
}
162194

163-
e <- rlang::caller_env()
164-
for (name in names(e)) {
165-
value <- e[[name]]
166-
if (inherits(value, CLASS_RDANTIC_MODEL_FIELD)) {
167-
e[[name]] <- value$default
195+
func_env <- rlang::caller_env()
196+
for (name in names(func_env)) {
197+
value <- func_env[[name]]
198+
if (inherits(value, CLASS_MODEL_FIELD)) {
199+
func_env[[name]] <- value$default
168200
}
169201
}
170202

171-
base_model(fields)(.x = e)
203+
base_model(fields)(.x = func_env)
172204
}
173205

174206
# ---
@@ -182,7 +214,7 @@ model_validate <- function(obj, model_fn) {
182214

183215
# ---
184216
#' @export
185-
print.rdantic <- function(x, ...) {
217+
print.typewriter <- function(x, ...) {
186218
print(x[seq_along(x)])
187219
return(invisible(x))
188220
}
@@ -199,7 +231,7 @@ check_assignment <- function(x, name, value) {
199231

200232
# ---
201233
#' @export
202-
`$<-.rdantic` <- function(x, name, value) {
234+
`$<-.typewriter` <- function(x, name, value) {
203235
if (isFALSE(name %in% names(x))) {
204236
return(x)
205237
}
@@ -210,7 +242,7 @@ check_assignment <- function(x, name, value) {
210242

211243
# ---
212244
#' @export
213-
`[[<-.rdantic` <- function(x, name, value) {
245+
`[[<-.typewriter` <- function(x, name, value) {
214246
if (isFALSE(name %in% names(x))) {
215247
return(x)
216248
}
@@ -226,36 +258,34 @@ check_assignment <- function(x, name, value) {
226258
# }
227259

228260
# ---
229-
# TODO: Deprecated?, use single functions as 'model_exclude_na'
261+
#' Convert model to base list
262+
#' @param obj A typewriter model object
263+
#' @param by_alias Use aliases for names.
264+
#' @param exclude_na Whether to exclude `NA` values.
265+
#' @param exclude_null Whether to exclude `NULL` values.
266+
#' @param ... **not used** at the moment.
267+
#' @returns base list object
268+
#' @export
230269
model_dump <- function(obj,
231-
exclude = NULL,
232-
include = NULL,
233-
exclude_na = FALSE,
270+
by_alias = FALSE,
234271
exclude_null = FALSE,
235-
by_alias = FALSE) {
236-
fields <- model_fields(obj)
237-
238-
if (is_not_null(exclude)) {
239-
obj <- purrr::discard_at(obj, exclude)
240-
}
241-
242-
if (is_not_null(include)) {
243-
obj <- purrr::keep_at(obj, include)
272+
exclude_na = FALSE,
273+
...) {
274+
if (isTRUE(by_alias)) {
275+
# return(dump_by_alias(obj))
276+
obj <- dump_by_alias(obj)
244277
}
245278

246-
if (isTRUE(exclude_na)) {
279+
if (exclude_na) {
247280
obj <- discard_this(obj, rlang::is_na)
248281
}
249282

250-
if (isTRUE(exclude_null)) {
251-
obj <- discard_this(obj, rlang::is_null)
252-
}
253-
254-
if (isTRUE(by_alias)) {
255-
obj <- dump_by_alias(obj, fields)
283+
if (exclude_null) {
284+
obj <- discard_this(obj, is.null)
256285
}
257286

258-
return(obj)
287+
return(unclass(obj))
288+
# return(model_to_list(obj)) # dumps NULLs!
259289
}
260290

261291
# ---

0 commit comments

Comments
 (0)