Commit cc0458f0 authored by Ivan Mazhukin's avatar Ivan Mazhukin

fix extension app inference

parent 7bf952ed
...@@ -44,7 +44,7 @@ code --install-extension epm-docker-test-runner-0.1.0.vsix ...@@ -44,7 +44,7 @@ code --install-extension epm-docker-test-runner-0.1.0.vsix
- `Rerun last command`: повторяет последнюю сгенерированную команду. - `Rerun last command`: повторяет последнюю сгенерированную команду.
- `Open log folder`: открывает `${XDG_STATE_HOME:-$HOME/.local/state}/epm-docker-test` или настроенный каталог логов. - `Open log folder`: открывает `${XDG_STATE_HOME:-$HOME/.local/state}/epm-docker-test` или настроенный каталог логов.
Когда расширение спрашивает имя приложения, оно пытается определить его по активному файлу, если файл находится в `play.d`, `pack.d` или `repack.d`. Сначала проверяются простые shell-переменные вроде `PRODUCT=rstudio` или `PKGNAME=rstudio`; если они не найдены, используется имя файла без расширения. Если приложение найдено, расширение показывает кликабельные варианты `OK` и `Cancel`; имя приложения можно поправить в поле выбора перед нажатием `OK`. Когда расширение спрашивает имя приложения, оно пытается определить его по активному файлу, если файл находится в `play.d`, `pack.d` или `repack.d`. Для `play.d` используется имя play-скрипта без расширения, а не `PKGNAME` внутри скрипта. Для `pack.d` и `repack.d` расширение ищет соответствующий play-скрипт по имени файла и простым переменным вроде `PRODUCT`, `PKGNAME` и `BASEPKGNAME`. Если приложение найдено, расширение показывает кликабельные варианты `OK` и `Cancel`; имя приложения можно поправить в поле выбора перед нажатием `OK`.
## Избранное ## Избранное
......
...@@ -678,28 +678,157 @@ function inferAppFromActiveEditor(eepmRoot) { ...@@ -678,28 +678,157 @@ function inferAppFromActiveEditor(eepmRoot) {
if (!['play.d', 'pack.d', 'repack.d'].includes(dirName)) { if (!['play.d', 'pack.d', 'repack.d'].includes(dirName)) {
return undefined; return undefined;
} }
if (path.extname(filePath) !== '.sh') {
return undefined;
}
const baseName = path.basename(filePath, path.extname(filePath)); const baseName = path.basename(filePath, path.extname(filePath));
const parsed = inferAppFromFileContent(filePath); const detectedEepmRoot = findEepmRootForScript(filePath) || eepmRoot;
const app = parsed || baseName; const app = dirName === 'play.d'
const relative = path.relative(eepmRoot, filePath); ? baseName
: inferPlayAppFromPackageScript(detectedEepmRoot, filePath, baseName) || baseName;
const relative = detectedEepmRoot ? path.relative(detectedEepmRoot, filePath) : '';
const source = relative && !relative.startsWith('..') ? relative : filePath; const source = relative && !relative.startsWith('..') ? relative : filePath;
return { app, source }; return { app, source };
} }
function inferAppFromFileContent(filePath) { function findEepmRootForScript(filePath) {
let current = path.dirname(filePath);
while (current && current !== path.dirname(current)) {
if (
fs.existsSync(path.join(current, 'play.d')) &&
fs.existsSync(path.join(current, 'pack.d')) &&
fs.existsSync(path.join(current, 'repack.d'))
) {
return current;
}
current = path.dirname(current);
}
return undefined;
}
function inferPlayAppFromPackageScript(eepmRoot, filePath, baseName) {
const playDir = eepmRoot ? path.join(eepmRoot, 'play.d') : undefined;
if (!playDir || !fs.existsSync(playDir)) {
return undefined;
}
const exact = findPlayScriptByName(playDir, baseName);
if (exact) {
return exact;
}
const packageVariables = readShellVariables(filePath, ['PRODUCT', 'PKGNAME', 'BASEPKGNAME', 'APP', 'APPNAME']);
const candidates = uniqueStrings([
baseName,
...Object.values(packageVariables).flatMap(splitShellWords)
]);
for (const candidate of candidates) {
const byName = findPlayScriptByName(playDir, candidate);
if (byName) {
return byName;
}
}
return findPlayScriptByPackageName(playDir, candidates);
}
function findPlayScriptByName(playDir, appName) {
if (!appName) {
return undefined;
}
const direct = path.join(playDir, `${appName}.sh`);
if (fs.existsSync(direct)) {
return appName;
}
const normalized = normalizeAppName(appName);
const scripts = listScriptNames(playDir);
return scripts.find((scriptName) => normalizeAppName(scriptName) === normalized);
}
function findPlayScriptByPackageName(playDir, candidates) {
const normalizedCandidates = new Set(candidates.map(normalizeAppName).filter(Boolean));
if (normalizedCandidates.size === 0) {
return undefined;
}
for (const scriptName of listScriptNames(playDir)) {
const filePath = path.join(playDir, `${scriptName}.sh`);
const variables = readShellVariables(filePath, ['PKGNAME', 'BASEPKGNAME', 'PRODUCT', 'PRODUCTALT']);
const values = uniqueStrings(Object.values(variables).flatMap(splitShellWords));
if (values.some((value) => normalizedCandidates.has(normalizeAppName(value)))) {
return scriptName;
}
}
return undefined;
}
function listScriptNames(dir) {
try {
return fs.readdirSync(dir)
.filter((name) => name.endsWith('.sh'))
.map((name) => path.basename(name, '.sh'))
.sort((left, right) => left.localeCompare(right));
} catch {
return [];
}
}
function readShellVariables(filePath, names) {
const result = {};
try { try {
const text = fs.readFileSync(filePath, 'utf8').slice(0, 8192); const lines = fs.readFileSync(filePath, 'utf8').slice(0, 8192).split(/\r?\n/);
for (const variable of ['PRODUCT', 'PKGNAME', 'APP', 'APPNAME']) { for (const variable of names) {
const match = text.match(new RegExp(`^${variable}=(["']?)([A-Za-z0-9._+-]+)\\1\\s*$`, 'm')); for (const line of lines) {
if (match) { const match = line.match(new RegExp(`^${variable}=(.*)$`));
return match[2]; if (match) {
result[variable] = cleanupShellValue(match[1]);
break;
}
} }
} }
} catch { } catch {
return undefined; return result;
} }
return undefined; return result;
}
function cleanupShellValue(value) {
let cleaned = value.trim();
const quote = cleaned[0];
if ((quote === '"' || quote === "'") && cleaned.endsWith(quote)) {
return cleaned.slice(1, -1).trim();
}
return cleaned.replace(/\s+#.*$/, '').trim();
}
function splitShellWords(value) {
if (!value || /[$`(){}]/.test(value)) {
return [];
}
return value
.split(/\s+/)
.map((item) => item.trim())
.filter(Boolean);
}
function uniqueStrings(values) {
const seen = new Set();
return values.filter((value) => {
if (!value || seen.has(value)) {
return false;
}
seen.add(value);
return true;
});
}
function normalizeAppName(value) {
return String(value || '').toLowerCase();
} }
function resolveWorkspacePath(value) { function resolveWorkspacePath(value) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment