UNPKG

@putout/operator-filesystem

Version:

🐊Putout operator adds ability to filesystem referenced variables that was not defined

445 lines (316 loc) 11.3 kB
'use strict'; const { join, basename, dirname, } = require('node:path'); const {types} = require('@putout/babel'); const tryCatch = require('try-catch'); const { setLiteralValue, getProperty, traverseProperties, } = require('@putout/operate'); const maybeFS = require('./maybe-fs'); const { createTypeProperty, createFilesProperty, createFilenameProperty, createContentProperty, } = require('./property'); const { isProgram, objectExpression, } = types; const isString = (a) => typeof a === 'string'; const {isArray} = Array; const maybeArray = (a) => isArray(a) ? a : [a]; const toBase64 = (content) => { const [e, result] = tryCatch(btoa, content); if (e) return btoa(escape(content)); return result; }; const fromBase64 = (content) => { if (content.includes(' ')) return content; const [e, decoded] = tryCatch(atob, content); if (!e) return unescape(decoded); return content; }; const getRegExp = (wildcard) => { const escaped = wildcard .replace(/\./g, '\\.') .replace(/\*/g, '.*') .replace('?', '.?'); return RegExp(`^${escaped}$`); }; module.exports.getParentDirectory = getParentDirectory; function getParentDirectory(filePath) { if (!filePath.parentPath) return null; const {parentPath} = filePath.parentPath.parentPath; if (isProgram(parentPath)) return null; return parentPath; } module.exports.findFile = findFile; function isExcluded({name, base, exclude}) { for (const currentExclude of exclude) { if (name === currentExclude || getRegExp(currentExclude).test(base)) return true; } return false; } function findFile(node, name, exclude = []) { checkName(name); const filePaths = []; const names = maybeArray(name); for (const filenamePath of traverseProperties(node, 'filename')) { const {value} = filenamePath.node.value; const base = basename(value); for (const name of names) { if (value === name || getRegExp(name).test(base)) { const path = filenamePath.parentPath; const excluded = isExcluded({ name, base, exclude, }); if (excluded) continue; filePaths.push(path); } } } return filePaths; } function checkName(name) { if (!isString(name) && !isArray(name)) throw Error(`☝️ Looks like you forget to pass the 'name' of a file to 'findFile(filePath: Path|FilePath, name: string | string[]): FilePath'`); } function getFilenamePath(filePath) { const filenamePath = getProperty(filePath, 'filename'); return filenamePath.get('value'); } function getFilename(filePath) { const {value} = getFilenamePath(filePath).node; return value; } module.exports.getFileType = getFileType; function getFileType(filePath) { const typePath = getProperty(filePath, 'type'); return typePath.node.value.value; } module.exports.getFileContent = getFileContent; function getFileContent(filePath) { const content = getProperty(filePath, 'content'); return [ Boolean(content), content?.node.value.value, ]; } module.exports.getFilename = getFilename; module.exports.renameFile = (filePath, name) => { const oldName = getFilename(filePath); const valuePath = getFilenamePath(filePath); const baseName = oldName .split('/') .pop(); const newName = name .split('/') .pop(); const newFilename = oldName.replace(baseName, newName); setLiteralValue(valuePath, newFilename); maybeFS.renameFile(oldName, newFilename); }; module.exports.removeFile = removeFile; function removeFile(filePath) { const filename = getFilename(filePath); if (!getParentDirectory(filePath)) return; filePath.remove(); maybeFS.removeFile(filename); } module.exports.removeEmptyDirectory = (dirPath) => { const type = getFileType(dirPath); if (type !== 'directory') return; let nextParentDir = dirPath; while (!readDirectory(dirPath).length) { const name = getFilename(dirPath); if (name === '/') break; nextParentDir = getParentDirectory(dirPath); if (!nextParentDir) break; removeFile(dirPath); dirPath = nextParentDir; } }; module.exports.moveFile = (filePath, dirPath) => { if (filePath === dirPath) return; const dirname = getFilename(dirPath); const filename = getFilename(filePath); const dirPathFiles = getFiles(dirPath); const filenamePath = getProperty(filePath, 'filename'); const basename = filename .split('/') .pop(); const newFilename = join(dirname, basename); maybeRemoveFile(dirPath, newFilename); setLiteralValue(filenamePath.get('value'), newFilename); dirPathFiles.node.value.elements.push(filePath.node); filePath.remove(); maybeFS.renameFile(filename, newFilename); }; module.exports.copyFile = (filePath, dirPath) => { const dirname = getFilename(dirPath); const filename = getFilename(filePath); const basename = filename .split('/') .pop(); const newFilename = join(dirname, basename); const [hasContent, content] = getFileContent(filePath); const copiedFile = objectExpression([ createTypeProperty('file'), createFilenameProperty(newFilename), hasContent && createContentProperty(content), ].filter(Boolean)); maybeRemoveFile(dirPath, newFilename); const dirPathFiles = getFiles(dirPath); dirPathFiles.node.value.elements.push(copiedFile); maybeFS.copyFile(filename, newFilename); }; function maybeRemoveFile(dirPath, filename) { const type = getFileType(dirPath); if (type !== 'directory') { const filename = getFilename(dirPath); throw Error(`☝️ Looks like '${filename}' is not a directory, but: '${type}'. Rename to '${filename}/'`); } const dirPathFiles = getProperty(dirPath, 'files'); const name = join(getFilename(dirPath), basename(filename)); const [fileToOverwrite] = findFile(dirPathFiles, name); if (!fileToOverwrite) return; fileToOverwrite.remove(); } module.exports.createFile = (dirPath, name, content) => { maybeRemoveFile(dirPath, name); const dirPathFiles = getFiles(dirPath); const parentFilename = getFilename(dirPath); const filename = join(parentFilename, name); const typeProperty = createTypeProperty('file'); const filenameProperty = createFilenameProperty(filename); const properties = [ typeProperty, filenameProperty, content && createContentProperty(content), ].filter(Boolean); dirPathFiles.node.value.elements.push(objectExpression(properties)); const filePath = dirPathFiles.get('value.elements').at(-1); if (isString(content)) writeFileContent(filePath, content); return filePath; }; const getFiles = (dirPath) => getProperty(dirPath, 'files'); module.exports.readDirectory = readDirectory; function readDirectory(dirPath) { const fileType = getFileType(dirPath); if (fileType !== 'directory') return []; return getFiles(dirPath).get('value.elements'); } module.exports.createDirectory = createDirectory; function createDirectory(dirPath, name) { const dirPathFiles = getFiles(dirPath); const parentFilename = getFilename(dirPath); const filename = join(parentFilename, name); const typeProperty = createTypeProperty('directory'); const filesProperty = createFilesProperty([]); const filenameProperty = createFilenameProperty(filename); dirPathFiles.node.value.elements.push(objectExpression([ typeProperty, filenameProperty, filesProperty, ])); maybeFS.createDirectory(filename); return dirPathFiles.get('value.elements').at(-1); } module.exports.readFileContent = (filePath) => { const fileType = getFileType(filePath); if (fileType === 'directory') return ''; const [hasContent, content] = getFileContent(filePath); if (hasContent) return fromBase64(content); const filename = getFilename(filePath); const fileContent = maybeFS.readFileContent(filename); const property = createContentProperty(toBase64(fileContent)); filePath.node.properties.push(property); return fileContent; }; module.exports.writeFileContent = writeFileContent; function writeFileContent(filePath, content) { const fileType = getFileType(filePath); if (fileType === 'directory') return; const filename = getFilename(filePath); maybeFS.writeFileContent(filename, content); const contentPath = getProperty(filePath, 'content'); if (contentPath) { setLiteralValue(contentPath.node.value, toBase64(content)); return; } const property = createContentProperty(toBase64(content)); filePath.node.properties.push(property); } module.exports.createNestedDirectory = (path, name) => { const rootPath = getRootDirectory(path); const dir = dirname(name); if (dir === getFilename(path)) return createDirectory(path, basename(name)); let currentDir = name; const rootDir = getFilename(rootPath); const directories = []; let prevDir = currentDir; while (currentDir !== rootDir) { directories.unshift(currentDir); prevDir = currentDir; currentDir = dirname(currentDir); if (currentDir === prevDir) { currentDir = rootDir; for (const [i, dir] of directories.entries()) { directories[i] = join(rootDir, dir); } directories.shift(); break; } } let lastDirectoryPath = findFile(rootPath, directories).at(-1) || rootPath; const lastDirectoryName = getFilename(lastDirectoryPath); const n = directories.length; let i = directories.indexOf(lastDirectoryName) + 1; for (; i < n; i++) { const name = basename(directories[i]); lastDirectoryPath = createDirectory(lastDirectoryPath, name); } return lastDirectoryPath; }; module.exports.getRootDirectory = getRootDirectory; function getRootDirectory(path) { let currentDirPath = getParentDirectory(path); if (!currentDirPath) return path; let prevPath = currentDirPath; while (currentDirPath = getParentDirectory(currentDirPath)) { prevPath = currentDirPath; } return prevPath; } module.exports.init = maybeFS.init; module.exports.deinit = maybeFS.deinit; module.exports.pause = maybeFS.pause; module.exports.start = maybeFS.start;