@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
JavaScript
"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