UNPKG

@mdfriday/foundry

Version:

The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.

354 lines 12.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PathParserUtils = exports.ConfigurablePathNormalizer = exports.DefaultFileExtensionChecker = exports.BasicPathNormalizer = exports.PathParsingNormalizer = exports.PathProcessorImpl = void 0; const type_1 = require("../type"); const pathcomponents_1 = require("./pathcomponents"); const path_1 = require("../entity/path"); /** * PathProcessorImpl implements the PathProcessor interface * Main entry point for path processing operations */ class PathProcessorImpl { constructor(normalizer, extChecker) { this.normalizer = normalizer || new PathParsingNormalizer(); this.extChecker = extChecker || new DefaultFileExtensionChecker(); } /** * Parse a path with component information */ parse(component, path) { // Handle empty paths let normalizedPath = path; if (!normalizedPath || normalizedPath === '') { normalizedPath = '/'; } const normalized = this.normalizer.normalize(normalizedPath); const pathComponents = this.createPathComponents(component, normalized, path); return new path_1.Path(pathComponents); } /** * Parse and return only the identity */ parseIdentity(component, path) { const parsed = this.parse(component, path); return { identifierBase: () => parsed.base() }; } /** * Parse and return base and base name without identifier */ parseBaseAndBaseNameNoIdentifier(component, path) { const parsed = this.parse(component, path); return [parsed.base(), parsed.baseNameNoIdentifier()]; } /** * Create PathComponents from path information */ createPathComponents(component, normalizedPath, originalPath) { // Keep the original component type as passed let actualComponent = component; let actualPath = normalizedPath; // Handle cases where the path includes the component if (normalizedPath.startsWith('/')) { const parts = normalizedPath.split('/').filter(p => p.length > 0); if (parts.length > 0) { const firstPart = parts[0]; const knownComponents = ['static', 'layouts', 'themes', 'archetypes', 'data', 'i18n', 'assets']; if (knownComponents.includes(firstPart) && component === 'content') { // Path contains component info, use it actualComponent = firstPart; } // Check for static asset directories even under content const staticDirs = ['images', 'assets', 'static', 'css', 'js', 'fonts']; if (staticDirs.includes(firstPart) && component === 'content') { actualComponent = 'static'; } } } // Extract filename and directory const lastSlash = actualPath.lastIndexOf('/'); const filename = lastSlash >= 0 ? actualPath.substring(lastSlash + 1) : actualPath; const directory = lastSlash >= 0 ? actualPath.substring(0, lastSlash) : ''; // Detect bundle type based on filename let bundleType = this.detectBundleType(filename, actualComponent); // Calculate positions for different path parts const positions = this.calculatePathPositions(actualPath); // Extract identifiers (extensions and language codes) const identifiers = this.extractIdentifiers(actualPath, filename); return new pathcomponents_1.PathComponentsImpl(originalPath, actualPath, positions, identifiers, bundleType, false, actualComponent); } /** * Detect bundle type based on filename and component */ detectBundleType(filename, component) { if (component !== 'content' && component !== 'archetypes') { return type_1.PathType.File; } // Extract base name without language and extension const parts = filename.split('.'); let baseName = parts[0]; // Check if this is an index or _index file with content extension if (baseName === 'index' && this.isContentFile(filename)) { return type_1.PathType.Leaf; } else if (baseName === '_index' && this.isContentFile(filename)) { return type_1.PathType.Branch; } else if (this.isContentFile(filename)) { return type_1.PathType.ContentSingle; } else { // Non-content files (like .txt, .png, etc.) are regular files, not content resources return type_1.PathType.File; } } /** * Check if file is a content file based on extension */ isContentFile(filename) { const ext = this.getFileExtension(filename); return ['md', 'html', 'markdown', 'mdown', 'mkd', 'mkdn', 'htm'].includes(ext.toLowerCase()); } /** * Get file extension without dot */ getFileExtension(filename) { const lastDot = filename.lastIndexOf('.'); if (lastDot > 0 && lastDot < filename.length - 1) { return filename.substring(lastDot + 1); } return ''; } /** * Calculate positions for different path parts * Exact replica of Go version - parse from right to left */ calculatePathPositions(normalizedPath) { let sectionHigh = -1; let containerLow = -1; let containerHigh = -1; let slashCount = 0; // Parse from right to left, just like Go version for (let i = normalizedPath.length - 1; i >= 0; i--) { const c = normalizedPath[i]; if (c === '/') { slashCount++; if (containerHigh === -1) { containerHigh = i + 1; } else if (containerLow === -1) { containerLow = i + 1; } if (i > 0) { sectionHigh = i; } } } return new pathcomponents_1.PathPositionsImpl(containerLow, containerHigh, sectionHigh, -1); } /** * Extract identifiers from filename (extensions and language codes) */ extractIdentifiers(normalizedPath, filename) { const identifiers = []; const basePath = normalizedPath.substring(0, normalizedPath.length - filename.length); const basePathLength = basePath.length; const parts = filename.split('.'); if (parts.length <= 1) { return identifiers; } let currentPos = basePathLength + parts[0].length; // Process each part after the base name for (let i = 1; i < parts.length; i++) { const part = parts[i]; const dotPos = currentPos; // Position of the dot const partStart = dotPos + 1; // Position after the dot const partEnd = partStart + part.length; // Add the part (without the dot) as an identifier identifiers.push(new pathcomponents_1.LowHighImpl(partStart, partEnd)); currentPos = partEnd; } return identifiers; } } exports.PathProcessorImpl = PathProcessorImpl; /** * PathParsingNormalizer implements normalization rules specifically for path parsing * Uses per-space-to-dash conversion for maintaining exact Hugo path behavior */ class PathParsingNormalizer { constructor(toLowerCase = true, replaceSpaces = true) { this.toLowerCase = toLowerCase; this.replaceSpaces = replaceSpaces; } normalize(path) { let result = path; // Handle Windows paths - convert backslashes to forward slashes result = result.replace(/\\/g, '/'); if (this.toLowerCase) { result = result.toLowerCase(); } if (this.replaceSpaces) { // For path parsing, each space becomes a dash (Hugo path behavior) result = result.replace(/\s/g, '-'); } return result; } } exports.PathParsingNormalizer = PathParsingNormalizer; /** * BasicPathNormalizer implements Hugo's basic normalization rules */ class BasicPathNormalizer { constructor(toLowerCase = true, replaceSpaces = true) { this.toLowerCase = toLowerCase; this.replaceSpaces = replaceSpaces; } normalize(path) { let result = path; // Handle Windows paths - convert backslashes to forward slashes result = result.replace(/\\/g, '/'); if (this.toLowerCase) { result = result.toLowerCase(); } if (this.replaceSpaces) { // For BasicPathNormalizer (general use), collapse consecutive whitespace to single dash result = result.replace(/\s+/g, '-'); } return result; } } exports.BasicPathNormalizer = BasicPathNormalizer; /** * DefaultFileExtensionChecker implements basic file extension checking */ class DefaultFileExtensionChecker { isContentExt(ext) { const contentExts = type_1.PATH_CONSTANTS.CONTENT_EXTENSIONS; return contentExts.includes(ext.toLowerCase()); } isHTML(ext) { const htmlExts = type_1.PATH_CONSTANTS.HTML_EXTENSIONS; return htmlExts.includes(ext.toLowerCase()); } hasExt(path) { for (let i = path.length - 1; i >= 0; i--) { if (path[i] === '.') { return true; } if (path[i] === '/') { return false; } } return false; } } exports.DefaultFileExtensionChecker = DefaultFileExtensionChecker; /** * ConfigurablePathNormalizer allows custom normalization rules */ class ConfigurablePathNormalizer { constructor(config) { this.rules = []; if (config?.normalizer) { this.rules.push(config.normalizer); } else { // Default rules if (config?.normalize !== false) { this.rules.push(path => path.toLowerCase()); } if (config?.replaceSpaces !== false) { this.rules.push(path => path.replace(/\s+/g, '-')); } } } normalize(path) { return this.rules.reduce((result, rule) => rule(result), path); } /** * Add a custom normalization rule */ addRule(rule) { this.rules.push(rule); } /** * Clear all rules */ clearRules() { this.rules = []; } } exports.ConfigurablePathNormalizer = ConfigurablePathNormalizer; /** * Path parsing utilities */ class PathParserUtils { /** * Parse a path string into basic components */ static parseBasic(path) { const lastSlash = path.lastIndexOf('/'); const dir = lastSlash >= 0 ? path.substring(0, lastSlash) : ''; const name = lastSlash >= 0 ? path.substring(lastSlash + 1) : path; const lastDot = name.lastIndexOf('.'); const ext = lastDot >= 0 ? name.substring(lastDot) : ''; const nameWithoutExt = lastDot >= 0 ? name.substring(0, lastDot) : name; return { dir, name, ext, nameWithoutExt }; } /** * Join path segments */ static join(...segments) { return segments .filter(segment => segment.length > 0) .map(segment => segment.replace(/^\/+|\/+$/g, '')) .join('/') .replace(/\/+/g, '/'); } /** * Normalize a path string using basic rules */ static normalizeBasic(path) { const normalizer = new BasicPathNormalizer(); return normalizer.normalize(path); } /** * Check if a path represents a bundle */ static isBundle(path) { const basic = PathParserUtils.parseBasic(path); const indexNames = type_1.PATH_CONSTANTS.INDEX_NAMES; return indexNames.includes(basic.nameWithoutExt); } /** * Extract section from path */ static extractSection(path) { const normalized = path.startsWith('/') ? path.substring(1) : path; const firstSlash = normalized.indexOf('/'); return firstSlash >= 0 ? normalized.substring(0, firstSlash) : normalized; } /** * Remove extension from path */ static removeExtension(path) { const lastDot = path.lastIndexOf('.'); const lastSlash = path.lastIndexOf('/'); // Only remove extension if dot comes after last slash if (lastDot > lastSlash) { return path.substring(0, lastDot); } return path; } /** * Check if path has extension */ static hasExtension(path) { const checker = new DefaultFileExtensionChecker(); return checker.hasExt(path); } } exports.PathParserUtils = PathParserUtils; //# sourceMappingURL=pathparser.js.map