@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
76 lines • 3.83 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveClassifyOptions = resolveClassifyOptions;
exports.isShinyAppSignals = isShinyAppSignals;
exports.classifyProjectKind = classifyProjectKind;
const project_kind_1 = require("./project-kind");
/** default DESCRIPTION `Type:` values marking a shiny app */
const shinyDescriptionTypes = new Set(['shiny', 'shiny-app', 'shinyapp']);
/** default (lower-cased) file names a shiny app is assembled from */
const shinyEntryFiles = new Set(['app.r', 'ui.r', 'server.r', 'global.r']);
/** default evidence that a file is part of a shiny app (a load of the shiny stack or a call to one of its entry points) */
const shinyUsage = /(?:library|require|requireNamespace|loadNamespace)\s*\(\s*['"]?(?:shiny|bslib|shinydashboard|shinyMobile|golem|flexdashboard)\b|\b(?:shinyApp|shinyServer|shinyUI|runApp|fluidPage|fluidRow|navbarPage|bootstrapPage|fillPage|dashboardPage|page_fluid|page_sidebar|page_navbar|pageWithSidebar)\s*\(/;
/** default extensions marking a notebook / literate document */
const notebookExtension = /\.(ipynb|rmd|rmarkdown|qmd|rnw)$/;
/** Resolve {@link ClassificationConfig} into {@link ResolvedClassifyOptions}, using the built-in defaults for unset fields. */
function resolveClassifyOptions(cfg) {
return {
shinyDescriptionTypes: cfg?.shinyDescriptionTypes ? new Set(cfg.shinyDescriptionTypes.map(s => s.toLowerCase())) : shinyDescriptionTypes,
shinyEntryFiles: cfg?.shinyEntryFiles ? new Set(cfg.shinyEntryFiles.map(s => s.toLowerCase())) : shinyEntryFiles,
shinyUsage: cfg?.shinyUsagePattern ? new RegExp(cfg.shinyUsagePattern) : shinyUsage,
notebookExtension: cfg?.notebookExtensions ? new RegExp(`\\.(${cfg.notebookExtensions.join('|')})$`, 'i') : notebookExtension
};
}
const defaultClassifyOptions = resolveClassifyOptions();
/**
* Whether the project is a shiny app: it has the canonical entry files (a single `app.R`, or the `ui.R`+`server.R`
* pair) and one of them uses the shiny stack. When no entry file can be read we trust the file names alone.
*/
function isShinyAppSignals(names, entries, shinyUsagePattern = shinyUsage) {
if (!names.has('app.r') && !(names.has('ui.r') && names.has('server.r'))) {
return false;
}
let couldRead = false;
for (const read of entries.values()) {
const content = read();
if (content === undefined) {
continue;
}
couldRead = true;
if (shinyUsagePattern.test(content)) {
return true;
}
}
return !couldRead;
}
/**
* Classify the {@link ProjectKind} from the given {@link ProjectKindSignals}. Shiny apps are detected first (they
* often ship a `DESCRIPTION` too), then packages, then notebooks; the rest is a single-file `Script` or a `Project`.
*/
function classifyProjectKind(signals, opts = defaultClassifyOptions) {
if (signals.descriptionTypes.some(t => opts.shinyDescriptionTypes.has(t))) {
return project_kind_1.ProjectKind.ShinyApp;
}
if (isShinyAppSignals(signals.names, signals.entries, opts.shinyUsage)) {
return project_kind_1.ProjectKind.ShinyApp;
}
if (signals.descriptionCount > 0) {
return project_kind_1.ProjectKind.Package;
}
if (signals.names.size === 0) {
return project_kind_1.ProjectKind.Unknown;
}
for (const name of signals.names) {
if (opts.notebookExtension.test(name)) {
return project_kind_1.ProjectKind.Notebook;
}
}
let rSources = 0;
for (const name of signals.names) {
if (name.endsWith('.r')) {
rSources++;
}
}
return rSources <= 1 ? project_kind_1.ProjectKind.Script : project_kind_1.ProjectKind.Project;
}
//# sourceMappingURL=classify-project-kind.js.map