beatrina 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +92 -0
- package/NOTICES +72 -0
- package/README.md +124 -0
- package/bin/beatrina.mjs +223 -0
- package/bin/cli.mjs +80 -0
- package/bin/failsafe.mjs +74 -0
- package/bin/identity.mjs +45 -0
- package/bin/prova-post.mjs +51 -0
- package/bin/sessions.mjs +95 -0
- package/bin/shortcut.mjs +151 -0
- package/bin/update-check.mjs +55 -0
- package/bin/upgrade.mjs +76 -0
- package/build-info.json +1 -0
- package/carmar_V0.8.6.html +1310 -0
- package/check/acceptance.mjs +278 -0
- package/check/session.mjs +215 -0
- package/engines/js/document-exec.mjs +82 -0
- package/engines/js/persist.mjs +214 -0
- package/engines/js/worker.mjs +424 -0
- package/engines/python/adapter.py +577 -0
- package/engines/python/analyze.py +814 -0
- package/engines/python/bootstrap.py +309 -0
- package/engines/python/dataview.py +735 -0
- package/engines/python/debugger.py +346 -0
- package/engines/python/document_exec.py +158 -0
- package/engines/python/engine.json +28 -0
- package/engines/python/handoff.py +118 -0
- package/engines/python/worker.py +564 -0
- package/engines/r/engine.json +25 -0
- package/engines/r/handoff.R +92 -0
- package/failsafe/ai-policy.R +255 -0
- package/failsafe/ai-store.R +373 -0
- package/failsafe/cite.R +418 -0
- package/failsafe/journal.R +684 -0
- package/failsafe/plugins.R +809 -0
- package/failsafe/serve.R +5500 -0
- package/host/ai-policy.mjs +218 -0
- package/host/deployment.mjs +160 -0
- package/host/engine-js.mjs +98 -0
- package/host/engine-pool.mjs +383 -0
- package/host/engine-python.mjs +228 -0
- package/host/engine-r.mjs +206 -0
- package/host/engine-stdio.mjs +401 -0
- package/host/journal-store.mjs +749 -0
- package/host/main.mjs +503 -0
- package/host/planes/README.md +41 -0
- package/host/planes/ai-store.mjs +327 -0
- package/host/planes/ai.mjs +467 -0
- package/host/planes/analyze.mjs +397 -0
- package/host/planes/cite.mjs +517 -0
- package/host/planes/files.mjs +0 -0
- package/host/planes/jobs.mjs +704 -0
- package/host/planes/journal.mjs +53 -0
- package/host/planes/latex.mjs +201 -0
- package/host/planes/mcp.mjs +493 -0
- package/host/planes/pair.mjs +325 -0
- package/host/planes/pipe-term.mjs +122 -0
- package/host/planes/plugins.mjs +112 -0
- package/host/planes/proc-tree.mjs +76 -0
- package/host/planes/sessions.mjs +434 -0
- package/host/planes/settings.mjs +164 -0
- package/host/planes/terminal.mjs +286 -0
- package/host/planes/test-file.mjs +80 -0
- package/host/planes/update.mjs +214 -0
- package/host/plugin-store.mjs +838 -0
- package/host/server.mjs +441 -0
- package/host/settings.mjs +379 -0
- package/host/update-record.mjs +59 -0
- package/host/user-dirs.mjs +117 -0
- package/host/windows-runtime.mjs +145 -0
- package/host/worker-plane.mjs +713 -0
- package/host/ws.mjs +190 -0
- package/kernel/analyze.R +668 -0
- package/kernel/deployment.R +165 -0
- package/kernel/examples/NOTICE.md +38 -0
- package/kernel/examples/tna-complete-tutorial.Rmd +210 -0
- package/kernel/fileio.R +656 -0
- package/kernel/index.html +96 -0
- package/kernel/job-run.R +391 -0
- package/kernel/jobs.R +276 -0
- package/kernel/kernel-protocol +1 -0
- package/kernel/kernel-version +1 -0
- package/kernel/kernel.R +671 -0
- package/kernel/knitr-run.R +245 -0
- package/kernel/latex.R +609 -0
- package/kernel/mcp/carmar-mcp.mjs +516 -0
- package/kernel/notebook-page.R +67 -0
- package/kernel/plugins/csl/apa/apa.csl +2273 -0
- package/kernel/plugins/csl/apa/plugin.json +19 -0
- package/kernel/plugins/csl/chicago-author-date/chicago-author-date.csl +4216 -0
- package/kernel/plugins/csl/chicago-author-date/plugin.json +19 -0
- package/kernel/plugins/csl/harvard-cite-them-right/harvard-cite-them-right.csl +316 -0
- package/kernel/plugins/csl/harvard-cite-them-right/plugin.json +19 -0
- package/kernel/plugins/csl/ieee/ieee.csl +519 -0
- package/kernel/plugins/csl/ieee/plugin.json +19 -0
- package/kernel/plugins/csl/modern-language-association/modern-language-association.csl +1184 -0
- package/kernel/plugins/csl/modern-language-association/plugin.json +19 -0
- package/kernel/plugins/csl/nature/nature.csl +189 -0
- package/kernel/plugins/csl/nature/plugin.json +19 -0
- package/kernel/plugins/latex/apa7/apa7.json +14 -0
- package/kernel/plugins/latex/apa7/plugin.json +19 -0
- package/kernel/plugins/latex/elsarticle/elsarticle.json +14 -0
- package/kernel/plugins/latex/elsarticle/plugin.json +19 -0
- package/kernel/plugins/latex/ieeetran/ieeetran.json +10 -0
- package/kernel/plugins/latex/ieeetran/plugin.json +19 -0
- package/kernel/project.R +131 -0
- package/kernel/settings.R +410 -0
- package/kernel/sniff.R +769 -0
- package/kernel/worker-boot.R +22 -0
- package/kernel/worker.R +3496 -0
- package/lib/agent-authoring-contract.js +547 -0
- package/lib/cell-kinds.js +108 -0
- package/lib/engine-labels.js +324 -0
- package/package.json +32 -0
package/kernel/jobs.R
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
#
|
|
2
|
+
# jobs.R — what a development task IS, decided without starting one.
|
|
3
|
+
#
|
|
4
|
+
# ── the shape, and why it is this shape ───────────────────────────────────
|
|
5
|
+
#
|
|
6
|
+
# Stage 5 slice 3 and Stage 6 item 7 ask for the same thing from two
|
|
7
|
+
# directions: package tasks that do not consume the interactive worker, and
|
|
8
|
+
# detached jobs that outlive the run that started them. Both are one model —
|
|
9
|
+
# a THIRD kind of child process, beside the worker (evaluates the user's
|
|
10
|
+
# notebook) and the analyzer (evaluates nothing at all).
|
|
11
|
+
#
|
|
12
|
+
# A job sits between them: it DOES evaluate code, which is why it may not be
|
|
13
|
+
# the analyzer, and it evaluates code that is not the notebook's, which is
|
|
14
|
+
# why it may not be the worker. `devtools::check()` runs the package's own
|
|
15
|
+
# tests in a fresh process; doing that in the session would mean the user's
|
|
16
|
+
# workspace is gone and their Stop button is a lie for four minutes.
|
|
17
|
+
#
|
|
18
|
+
# ── the one security decision in this file ────────────────────────────────
|
|
19
|
+
#
|
|
20
|
+
# THE WIRE NEVER CARRIES CODE. The browser sends a task NAME out of a fixed
|
|
21
|
+
# vocabulary and a directory; this file decides whether that pair is
|
|
22
|
+
# runnable, and the supervisor builds the argument vector. Nothing from the
|
|
23
|
+
# socket is ever pasted into an R expression or a shell string — task names
|
|
24
|
+
# are matched against a list, and the root and filter travel in the child's
|
|
25
|
+
# ENVIRONMENT, where they are data to `Sys.getenv()` and can never be parsed.
|
|
26
|
+
#
|
|
27
|
+
# That is the same rule spike/broker.R follows for systemctl, and it is worth
|
|
28
|
+
# stating as a rule rather than a habit: `exec` already lets a page run
|
|
29
|
+
# arbitrary R, so this is not the only door to evaluation — but it is a door
|
|
30
|
+
# whose vocabulary is small enough to read in one screen, and keeping it that
|
|
31
|
+
# way is cheaper than auditing an interpolation.
|
|
32
|
+
#
|
|
33
|
+
# Pure by construction: nothing here starts a process, writes a file, or
|
|
34
|
+
# reads a socket, so spike/test-jobs.R can exercise every branch without one.
|
|
35
|
+
|
|
36
|
+
#' Is this directory the root of an R package?
|
|
37
|
+
#'
|
|
38
|
+
#' The test is a DESCRIPTION carrying a `Package:` field, not merely a file
|
|
39
|
+
#' called DESCRIPTION — Bioconductor build dirs, some data packages and a
|
|
40
|
+
#' stray copied file all put that name somewhere it does not mean this.
|
|
41
|
+
#'
|
|
42
|
+
#' A DESCRIPTION that cannot be parsed answers FALSE. That is a decision, not
|
|
43
|
+
#' a swallowed error: "this is not a package root" is exactly the right
|
|
44
|
+
#' answer for a malformed file, and the caller's next step (say so, offer the
|
|
45
|
+
#' folder anyway) is the same either way.
|
|
46
|
+
#'
|
|
47
|
+
#' @param dir Directory to test.
|
|
48
|
+
#' @return TRUE when `dir` is an R package root.
|
|
49
|
+
is_package_root <- function(dir) {
|
|
50
|
+
desc <- file.path(dir, "DESCRIPTION")
|
|
51
|
+
if (!file.exists(desc) || dir.exists(desc)) return(FALSE)
|
|
52
|
+
fields <- tryCatch(read.dcf(desc, fields = "Package"),
|
|
53
|
+
error = function(e) NULL,
|
|
54
|
+
warning = function(w) NULL)
|
|
55
|
+
!is.null(fields) && nrow(fields) >= 1L &&
|
|
56
|
+
!is.na(fields[1L, 1L]) && nzchar(trimws(fields[1L, 1L]))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#' Every directory from `path` up to the filesystem root.
|
|
60
|
+
#'
|
|
61
|
+
#' A parent chain cannot be vectorised — each step is computed from the last —
|
|
62
|
+
#' so this is a bounded `while`, and the bound is the point: a symlink loop or
|
|
63
|
+
#' a pathological path must not spin the supervisor's event loop.
|
|
64
|
+
#'
|
|
65
|
+
#' @param path Starting path.
|
|
66
|
+
#' @param max_up Most ancestors to walk.
|
|
67
|
+
#' @return Character vector, nearest first.
|
|
68
|
+
path_ancestors <- function(path, max_up = 40L) {
|
|
69
|
+
out <- character(0)
|
|
70
|
+
current <- path
|
|
71
|
+
while (length(out) < max_up) {
|
|
72
|
+
out <- c(out, current)
|
|
73
|
+
parent <- dirname(current)
|
|
74
|
+
if (identical(parent, current)) break # "/" is its own dirname
|
|
75
|
+
current <- parent
|
|
76
|
+
}
|
|
77
|
+
out
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#' Find the package root at or above a path.
|
|
81
|
+
#'
|
|
82
|
+
#' @param path A file or directory inside the package.
|
|
83
|
+
#' @return The package root, or "" when there is none.
|
|
84
|
+
find_package_root <- function(path) {
|
|
85
|
+
if (!is.character(path) || length(path) != 1L || is.na(path) || !nzchar(path)) return("")
|
|
86
|
+
start <- if (dir.exists(path)) path else dirname(path)
|
|
87
|
+
start <- tryCatch(normalizePath(start, mustWork = FALSE), error = function(e) start)
|
|
88
|
+
Find(is_package_root, path_ancestors(start)) %||% ""
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#' The package's name, for a label a person recognises.
|
|
92
|
+
#'
|
|
93
|
+
#' @param root A package root.
|
|
94
|
+
#' @return The `Package:` field, or "" when unreadable.
|
|
95
|
+
package_name <- function(root) {
|
|
96
|
+
if (!nzchar(root) || !is_package_root(root)) return("")
|
|
97
|
+
fields <- tryCatch(read.dcf(file.path(root, "DESCRIPTION"), fields = "Package"),
|
|
98
|
+
error = function(e) NULL, warning = function(w) NULL)
|
|
99
|
+
# unname(): read.dcf() returns a matrix whose dimnames survive the [1,1]
|
|
100
|
+
# extraction, so the "obvious" version returns a string NAMED "Package".
|
|
101
|
+
# It prints identically, compares unequal to the plain string, and is the
|
|
102
|
+
# kind of stowaway attribute that turns into a JSON object on the wire.
|
|
103
|
+
if (is.null(fields)) "" else unname(trimws(fields[1L, 1L]))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# ── the vocabulary ──────────────────────────────────────────────────────────
|
|
107
|
+
#
|
|
108
|
+
# Every task declares WHAT IT ACTS ON, and that field is the reason this list
|
|
109
|
+
# is a table rather than a vector of names. Six tasks act on a package root;
|
|
110
|
+
# `render` acts on a single document, which has no DESCRIPTION and never
|
|
111
|
+
# will. Before `target` existed, `job_spec()` demanded a package root for
|
|
112
|
+
# everything — so rendering a standalone .qmd was refused for not being an R
|
|
113
|
+
# package, which is a true statement and a useless one.
|
|
114
|
+
#
|
|
115
|
+
# `pkg` is the package the task needs INSTALLED. It is carried so the child
|
|
116
|
+
# can say "devtools is not installed" instead of failing with an
|
|
117
|
+
# object-not-found deep inside a call the reader never made. `render` has no
|
|
118
|
+
# fixed answer there — it depends on the document — so it carries NA and the
|
|
119
|
+
# child decides; see pick_renderer() in job-run.R.
|
|
120
|
+
JOB_TASKS <- list(
|
|
121
|
+
load = list(label = "Load", running = "Loading", target = "package", pkg = "pkgload"),
|
|
122
|
+
document = list(label = "Document", running = "Documenting", target = "package", pkg = "devtools"),
|
|
123
|
+
test = list(label = "Test", running = "Testing", target = "package", pkg = "testthat"),
|
|
124
|
+
check = list(label = "Check", running = "Checking", target = "package", pkg = "devtools"),
|
|
125
|
+
build = list(label = "Build", running = "Building", target = "package", pkg = "devtools"),
|
|
126
|
+
install = list(label = "Install", running = "Installing", target = "package", pkg = "devtools"),
|
|
127
|
+
render = list(label = "Render", running = "Rendering", target = "document", pkg = NA_character_),
|
|
128
|
+
latex = list(label = "Compile", running = "Compiling", target = "document", pkg = NA_character_)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
#' Documents this kernel will render. Lower-cased extension, no dot.
|
|
132
|
+
#'
|
|
133
|
+
#' `.md` is here because both renderers accept it; a plain Markdown file with
|
|
134
|
+
#' no chunks still renders, and refusing it would be a distinction the reader
|
|
135
|
+
#' does not share.
|
|
136
|
+
RENDER_EXTENSIONS <- c("qmd", "rmd", "md")
|
|
137
|
+
|
|
138
|
+
# The formats offered, and "" FIRST because it is the default and the default
|
|
139
|
+
# matters: an empty format passes no `--to` at all, so the document's own YAML
|
|
140
|
+
# decides. A document whose header says `format: revealjs` must render as
|
|
141
|
+
# revealjs when the reader presses Render without choosing anything —
|
|
142
|
+
# defaulting to html would silently overrule the file.
|
|
143
|
+
RENDER_FORMATS <- c("", "html", "pdf", "docx")
|
|
144
|
+
|
|
145
|
+
#' Is this a single, plain, non-empty string? Mirrors serve.R's scalar_chr —
|
|
146
|
+
#' R's coercion rules are exactly permissive enough to turn a JSON array into
|
|
147
|
+
#' something that runs but is not what anybody meant.
|
|
148
|
+
#' @param x Value from the wire.
|
|
149
|
+
#' @return TRUE for a length-1 non-NA character vector.
|
|
150
|
+
job_scalar <- function(x) is.character(x) && length(x) == 1L && !is.na(x)
|
|
151
|
+
|
|
152
|
+
# A test filter is a regular expression testthat matches against file names.
|
|
153
|
+
# It is data, never code — but it is still bounded and stripped of control
|
|
154
|
+
# characters, because it is echoed into a label the UI renders and into the
|
|
155
|
+
# audit stream, where a newline would forge a record.
|
|
156
|
+
JOB_FILTER_MAX <- 200L
|
|
157
|
+
|
|
158
|
+
#' The lower-cased extension of a path, with no dot.
|
|
159
|
+
#' @param path A file path.
|
|
160
|
+
#' @return The extension, or "".
|
|
161
|
+
path_extension <- function(path) {
|
|
162
|
+
base <- basename(path)
|
|
163
|
+
if (!grepl(".", base, fixed = TRUE)) return("")
|
|
164
|
+
tolower(sub("^.*\\.", "", base))
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#' Decide whether one task may run, and exactly what would run.
|
|
168
|
+
#'
|
|
169
|
+
#' The whole decision in one pure function, returning it whole — the same
|
|
170
|
+
#' shape as `carmar_deployment()`, and for the same reason: three callers
|
|
171
|
+
#' (the supervisor, the tests, the UI's error message) must not each
|
|
172
|
+
#' re-derive it and drift.
|
|
173
|
+
#'
|
|
174
|
+
#' `target` is the ONE thing the task acts on: a package root for a package
|
|
175
|
+
#' task, a document for `render`. Which of those it must be is the task's own
|
|
176
|
+
#' declaration, never the caller's guess.
|
|
177
|
+
#'
|
|
178
|
+
#' @param task Task name from the wire.
|
|
179
|
+
#' @param target Directory or file from the wire.
|
|
180
|
+
#' @param opts Named list of extras from the wire: `filter` (test), `format`
|
|
181
|
+
#' (render). Unknown members are ignored rather than refused — a page one
|
|
182
|
+
#' version ahead must not be an error.
|
|
183
|
+
#' @return list(ok, error, task, target, root, label, package, env) — `env` is
|
|
184
|
+
#' the named character vector of environment variables for the child, and is
|
|
185
|
+
#' the ONLY channel by which wire data reaches it.
|
|
186
|
+
job_spec <- function(task, target, opts = list()) {
|
|
187
|
+
bad <- function(msg) list(ok = FALSE, error = msg)
|
|
188
|
+
if (!job_scalar(task) || !nzchar(task)) return(bad("no task was named"))
|
|
189
|
+
if (!task %in% names(JOB_TASKS)) {
|
|
190
|
+
return(bad(sprintf("'%s' is not a task this kernel runs (%s)",
|
|
191
|
+
task, paste(names(JOB_TASKS), collapse = ", "))))
|
|
192
|
+
}
|
|
193
|
+
spec <- JOB_TASKS[[task]]
|
|
194
|
+
if (!job_scalar(target) || !nzchar(target)) {
|
|
195
|
+
return(bad(if (identical(spec$target, "document")) "no document was named"
|
|
196
|
+
else "no folder was named"))
|
|
197
|
+
}
|
|
198
|
+
filter <- if (job_scalar(opts$filter)) opts$filter else ""
|
|
199
|
+
format <- if (job_scalar(opts$format)) opts$format else ""
|
|
200
|
+
latex_env <- if (identical(task, "latex")) tryCatch(latex_options(opts), error = function(e) e) else character()
|
|
201
|
+
if (inherits(latex_env, "error")) return(bad(conditionMessage(latex_env)))
|
|
202
|
+
|
|
203
|
+
if (identical(spec$target, "package")) {
|
|
204
|
+
if (!dir.exists(target)) return(bad(sprintf("'%s' is not a folder", target)))
|
|
205
|
+
target <- tryCatch(normalizePath(target, mustWork = TRUE), error = function(e) target)
|
|
206
|
+
if (!is_package_root(target)) {
|
|
207
|
+
return(bad(paste0("'", target, "' is not an R package — no DESCRIPTION with a Package field. ",
|
|
208
|
+
"Open a file inside the package and try again.")))
|
|
209
|
+
}
|
|
210
|
+
root <- target
|
|
211
|
+
} else {
|
|
212
|
+
if (!file.exists(target) || dir.exists(target)) {
|
|
213
|
+
return(bad(sprintf("'%s' is not a file", target)))
|
|
214
|
+
}
|
|
215
|
+
target <- tryCatch(normalizePath(target, mustWork = TRUE), error = function(e) target)
|
|
216
|
+
ext <- path_extension(target)
|
|
217
|
+
extensions <- if (identical(task, "latex")) "tex" else RENDER_EXTENSIONS
|
|
218
|
+
if (!ext %in% extensions) {
|
|
219
|
+
return(bad(sprintf("CarmaR renders %s files; '%s' is a .%s",
|
|
220
|
+
paste0(".", extensions, collapse = ", "), basename(target),
|
|
221
|
+
if (nzchar(ext)) ext else "(no extension)")))
|
|
222
|
+
}
|
|
223
|
+
# The document's own folder, so a job still has one place it belongs —
|
|
224
|
+
# the pane labels by it, and a relative output path resolves against it.
|
|
225
|
+
root <- dirname(target)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (nzchar(filter) && !identical(task, "test")) {
|
|
229
|
+
return(bad("a filter only applies to the test task"))
|
|
230
|
+
}
|
|
231
|
+
filter <- gsub("[[:cntrl:]]", "", filter)
|
|
232
|
+
if (nchar(filter) > JOB_FILTER_MAX) return(bad("the test filter is too long"))
|
|
233
|
+
|
|
234
|
+
if (nzchar(format) && !identical(task, "render")) {
|
|
235
|
+
return(bad("a format only applies to the render task"))
|
|
236
|
+
}
|
|
237
|
+
if (!format %in% RENDER_FORMATS) {
|
|
238
|
+
return(bad(sprintf("'%s' is not a format this kernel renders (%s)",
|
|
239
|
+
format, paste(setdiff(RENDER_FORMATS, ""), collapse = ", "))))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
list(
|
|
243
|
+
ok = TRUE, error = "",
|
|
244
|
+
task = task, target = target, root = root,
|
|
245
|
+
label = spec$label, running = spec$running, targets = spec$target,
|
|
246
|
+
package = if (identical(spec$target, "package")) package_name(root) else "",
|
|
247
|
+
needs = spec$pkg,
|
|
248
|
+
env = c(CARMAR_JOB_TASK = task, CARMAR_JOB_TARGET = target,
|
|
249
|
+
CARMAR_JOB_ROOT = root, CARMAR_JOB_FILTER = filter,
|
|
250
|
+
CARMAR_JOB_FORMAT = format, latex_env)
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#' A sentence naming what is about to happen, before it happens.
|
|
255
|
+
#'
|
|
256
|
+
#' The same principle as Stage 6's `describePlan`: nothing runs unannounced,
|
|
257
|
+
#' and a label the reader cannot predict from the button they pressed is a
|
|
258
|
+
#' click-through waiting to happen.
|
|
259
|
+
#'
|
|
260
|
+
#' @param spec A successful `job_spec()`.
|
|
261
|
+
#' @return One line of text.
|
|
262
|
+
job_title <- function(spec) {
|
|
263
|
+
if (identical(spec$task, "latex")) return(sprintf("Compiling %s", basename(spec$target)))
|
|
264
|
+
if (identical(spec$targets, "document")) {
|
|
265
|
+
format <- spec$env[["CARMAR_JOB_FORMAT"]]
|
|
266
|
+
return(sprintf("Rendering %s%s", basename(spec$target),
|
|
267
|
+
if (nzchar(format)) paste0(" to ", format) else ""))
|
|
268
|
+
}
|
|
269
|
+
name <- if (nzchar(spec$package)) spec$package else basename(spec$root)
|
|
270
|
+
filter <- spec$env[["CARMAR_JOB_FILTER"]]
|
|
271
|
+
if (identical(spec$task, "test") && nzchar(filter)) {
|
|
272
|
+
sprintf("%s %s — tests matching '%s'", spec$running, name, filter)
|
|
273
|
+
} else {
|
|
274
|
+
sprintf("%s %s", spec$running, name)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.8.6
|