@mdfriday/foundry
Version:
The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.
450 lines • 16.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PathUtils = exports.Path = void 0;
const type_1 = require("../type");
const pathcomponents_1 = require("../vo/pathcomponents");
/**
* Path entity implementation
* Represents a parsed path with all its components and operations
*/
class Path {
constructor(components) {
this.components = components;
this.shouldTrimLeadingSlash = false;
}
// PathInfo implementation
component() {
// Use the component stored in PathComponents if available
if (this.components.component) {
return this.components.component;
}
// Fallback to parsing from path for legacy support
const path = this.components.normalized;
const parts = path.split('/').filter(p => p.length > 0);
if (parts.length === 0) {
return 'content'; // Root path defaults to content
}
const firstPart = parts[0];
// Map known Hugo components
const componentMap = {
'static': 'static',
'layouts': 'layouts',
'themes': 'themes',
'archetypes': 'archetypes',
'data': 'data',
'i18n': 'i18n',
'assets': 'assets'
};
return componentMap[firstPart] || 'content';
}
path() {
return this.norm(this.components.normalized);
}
name() {
if (this.components.positions.containerHigh > 0) {
return this.components.normalized.substring(this.components.positions.containerHigh);
}
return this.components.normalized;
}
nameNoExt() {
const firstId = this.components.firstIdentifier();
if (firstId) {
return this.components.normalized.substring(this.components.positions.containerHigh, firstId.low - 1);
}
return this.components.normalized.substring(this.components.positions.containerHigh);
}
nameNoLang() {
// Find language identifier position
if (this.components.identifiers.length >= 2) {
const lastId = this.components.identifiers[this.components.identifiers.length - 1]; // extension
const secondToLastId = this.components.identifiers[this.components.identifiers.length - 2]; // potential language
const langStr = this.components.normalized.substring(secondToLastId.low, secondToLastId.high);
const knownLangCodes = ['en', 'fr', 'es', 'de', 'zh', 'ja', 'ko', 'pt', 'it', 'ru', 'ar'];
if (knownLangCodes.includes(langStr)) {
// Remove the language part (including the dot before it)
const nameStart = this.components.positions.containerHigh;
const beforeLang = this.components.normalized.substring(nameStart, secondToLastId.low - 1);
const afterLang = this.components.normalized.substring(lastId.low - 1); // include dot before extension
return beforeLang + afterLang;
}
}
return this.name();
}
dir() {
let d = '';
if (this.components.positions.containerHigh > 0) {
d = this.components.normalized.substring(0, this.components.positions.containerHigh - 1);
}
if (d === '') {
d = '/';
}
return this.norm(d);
}
ext() {
if (this.components.identifiers.length === 0) {
return '';
}
// Extension is always the last identifier
const lastIdentifier = this.components.identifiers[this.components.identifiers.length - 1];
const extStr = this.components.normalized.substring(lastIdentifier.low, lastIdentifier.high);
return extStr ? '.' + extStr : '';
}
lang() {
// Look for language identifier (typically second to last identifier before extension)
if (this.components.identifiers.length >= 2) {
const secondToLastId = this.components.identifiers[this.components.identifiers.length - 2];
const langStr = this.components.normalized.substring(secondToLastId.low, secondToLastId.high);
// Check if it's a known language code
const knownLangCodes = ['en', 'fr', 'es', 'de', 'zh', 'ja', 'ko', 'pt', 'it', 'ru', 'ar'];
if (knownLangCodes.includes(langStr)) {
return '.' + langStr;
}
}
return '';
}
section() {
if (this.components.positions.sectionHigh <= 0) {
return '';
}
const sectionPath = this.components.normalized.substring(1, this.components.positions.sectionHigh);
// For root index files like /_index.md, section should be empty
if (sectionPath === '_index.md' || sectionPath === 'index.md' ||
sectionPath.endsWith('/_index.md') || sectionPath.endsWith('/index.md')) {
return '';
}
return this.norm(sectionPath);
}
sections() {
// Get the directory path (everything before the filename)
const dirPath = this.dir();
// If directory is root, return empty array
if (dirPath === '/' || dirPath === '') {
return [];
}
// Remove leading slash and split by slash
const cleanPath = dirPath.startsWith('/') ? dirPath.substring(1) : dirPath;
const sections = [];
const pathParts = cleanPath.split('/').filter(part => part.length > 0);
// Build cumulative paths: docs -> docs/table-of-contents -> etc.
let currentPath = '';
for (const part of pathParts) {
if (currentPath === '') {
currentPath = part;
}
else {
currentPath += '/' + part;
}
sections.push(this.norm(currentPath));
}
return sections;
}
container() {
if (this.components.positions.containerLow === -1) {
return '';
}
return this.norm(this.components.normalized.substring(this.components.positions.containerLow, this.components.positions.containerHigh - 1));
}
containerDir() {
if (this.isLeafBundle()) {
// For leaf bundles like /blog/my-post/index.md, return the parent dir (/blog)
// const dirPath = this.dir();
// const lastSlash = dirPath.lastIndexOf('/');
// if (lastSlash <= 0) {
// return '/';
// }
// return dirPath.substring(0, lastSlash);
return this.dir();
}
else if (this.isBranchBundle()) {
// For branch bundles like /blog/_index.md, return the dir itself (/blog)
return this.dir();
}
else {
// For single pages like /blog/post.md, return the dir (/blog)
return this.dir();
}
}
// PathOperations implementation
base() {
if (this.isBranchBundle() && this.components.normalized === '/_index.md') {
// Root branch bundle should return "/"
return '/';
}
else if (this.isLeafBundle()) {
return this.baseInternal(false, true);
}
else if (this.isContent() && !this.isBundle()) {
// For content single files, remove extension
return this.pathNoIdentifier();
}
else if (!this.isContent()) {
// For non-content files, preserve the full path including extension
return this.path();
}
else if (this.isBundle()) {
// For bundles, return path to container
return this.baseInternal(false, true);
}
return this.baseInternal(!this.isContentPage(), this.isBundle());
}
baseNoLeadingSlash() {
return this.base().substring(1);
}
baseNameNoIdentifier() {
if (this.isBundle()) {
return this.container();
}
return this.nameNoIdentifier();
}
nameNoIdentifier() {
if (this.components.identifiers.length === 0) {
return this.name();
}
// Remove all identifiers from the name
const firstIdentifier = this.components.identifiers[0];
const nameStart = this.components.positions.containerHigh;
// Find the dot before the first identifier and take everything before it
return this.components.normalized.substring(nameStart, firstIdentifier.low - 1);
}
pathNoLang() {
return this.baseInternal(true, false);
}
pathNoIdentifier() {
if (this.components.identifiers.length === 0) {
return this.path();
}
// Remove all identifiers from the path
const firstIdentifier = this.components.identifiers[0];
// Find the dot before the first identifier
const pathBeforeIdentifiers = this.components.normalized.substring(0, firstIdentifier.low - 1);
return this.norm(pathBeforeIdentifiers);
}
pathRel(owner) {
let ob = owner.base();
if (!ob.endsWith('/')) {
ob += '/';
}
return this.path().replace(new RegExp('^' + this.escapeRegExp(ob)), '');
}
baseRel(owner) {
let ob = owner.base();
if (ob === '/') {
ob = '';
}
return this.base().substring(ob.length + 1);
}
trimLeadingSlash() {
const clonedComponents = this.components.clone();
const newPath = new Path(clonedComponents);
newPath.setShouldTrimLeadingSlash(true);
return newPath;
}
identifier(index) {
// Reorder identifiers: extension first, then language codes
const totalIdentifiers = this.components.identifiers.length;
if (totalIdentifiers === 0 || index < 0 || index >= totalIdentifiers) {
return '';
}
let actualIndex;
if (totalIdentifiers === 1) {
// Only one identifier (extension)
actualIndex = 0;
}
else {
// Multiple identifiers: reverse order (extension first, then language codes)
if (index === 0) {
// First requested = last actual (extension)
actualIndex = totalIdentifiers - 1;
}
else {
// Other indices = totalIdentifiers - 1 - index
actualIndex = totalIdentifiers - 1 - index;
}
}
const identifierStr = this.identifierAsString(actualIndex);
return identifierStr ? '.' + identifierStr : '';
}
identifiers() {
// Return identifiers in reordered format (extension first)
const result = [];
const totalIdentifiers = this.components.identifiers.length;
for (let i = 0; i < totalIdentifiers; i++) {
const identifier = this.identifier(i);
if (identifier) {
result.push(identifier);
}
}
return result;
}
// PathMetadata implementation
bundleType() {
return this.components.bundleType;
}
isContent() {
return this.bundleType() >= type_1.PathType.ContentResource;
}
isBundle() {
return this.bundleType() > type_1.PathType.Leaf;
}
isBranchBundle() {
return this.bundleType() === type_1.PathType.Branch;
}
isLeafBundle() {
return this.bundleType() === type_1.PathType.Leaf;
}
isHTML() {
const ext = this.ext().toLowerCase();
return type_1.PATH_CONSTANTS.HTML_EXTENSIONS.some(htmlExt => htmlExt === ext);
}
disabled() {
return this.components.disabled;
}
forBundleType(type) {
const newComponents = this.components.withBundleType(type);
return new Path(newComponents);
}
// Main Path interface implementation
unnormalized() {
if (!this._unnormalized) {
// If original and normalized are the same, return self
if (this.components.original === this.components.normalized) {
this._unnormalized = this;
}
else {
// Create unnormalized version
const unnormalizedComponents = new pathcomponents_1.PathComponentsImpl(this.components.original, this.components.original, this.components.positions, this.components.identifiers, this.components.bundleType, this.components.disabled);
this._unnormalized = new Path(unnormalizedComponents);
}
}
return this._unnormalized;
}
// Private helper methods
setShouldTrimLeadingSlash(value) {
this.shouldTrimLeadingSlash = value;
}
norm(s) {
if (this.shouldTrimLeadingSlash) {
return s.startsWith('/') ? s.substring(1) : s;
}
return s;
}
isContentPage() {
return this.bundleType() >= type_1.PathType.ContentSingle;
}
baseInternal(preserveExt, isBundle) {
if (this.components.identifiers.length === 0) {
return this.norm(this.components.normalized);
}
if (preserveExt && this.components.identifiers.length === 1) {
return this.norm(this.components.normalized);
}
const lastId = this.components.identifiers[this.components.identifiers.length - 1];
let high = lastId.low - 1;
if (isBundle) {
high = this.components.positions.containerHigh - 1;
}
if (high === 0) {
high++;
}
if (!preserveExt) {
return this.norm(this.components.normalized.substring(0, high));
}
// For files like txt, preserve the extension
const firstId = this.components.identifiers[0];
return this.norm(this.components.normalized.substring(0, high) +
this.components.normalized.substring(firstId.low - 1, firstId.high));
}
identifierAsString(i) {
const index = this.identifierIndex(i);
if (index === -1) {
return '';
}
const id = this.components.identifiers[index];
return this.components.normalized.substring(id.low, id.high);
}
identifierIndex(i) {
if (i < 0 || i >= this.components.identifiers.length) {
return -1;
}
return i;
}
escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// Utility methods
toString() {
return `Path{path="${this.path()}", component="${this.component()}", bundleType=${type_1.PathType[this.bundleType()]}}`;
}
/**
* Check if two paths are equal
*/
equals(other) {
return this.path() === other.path() &&
this.component() === other.component() &&
this.bundleType() === other.bundleType();
}
/**
* Get a hash code for this path
*/
hashCode() {
return `${this.component()}:${this.path()}:${this.bundleType()}`;
}
}
exports.Path = Path;
/**
* Utility functions for working with Path instances
*/
class PathUtils {
/**
* Create a path from string components
*/
static fromString(component, path) {
const components = new pathcomponents_1.PathComponentsImpl(path, path, { containerLow: -1, containerHigh: -1, sectionHigh: -1, identifierLanguage: -1 }, [], type_1.PathType.File, false);
return new Path(components);
}
/**
* Check if a path has a specific extension
*/
static hasExtension(path, extension) {
const pathExt = path.ext();
const targetExt = extension.startsWith('.') ? extension : '.' + extension;
return pathExt.toLowerCase() === targetExt.toLowerCase();
}
/**
* Check if child path is under parent path
*/
static isUnder(child, parent) {
const childPath = child.path();
let parentPath;
// For branch bundles like /blog/_index.md, the parent path is the directory (/blog)
if (parent.isBranchBundle()) {
parentPath = parent.dir();
}
else {
parentPath = parent.path();
}
// Ensure paths end with slash for proper comparison
const normalizedParent = parentPath === '/' ? '/' : parentPath + '/';
return childPath !== parentPath && childPath.startsWith(normalizedParent);
}
/**
* Get the relative path from one path to another
*/
static relativeTo(from, to) {
return to.pathRel(from);
}
/**
* Compare two paths for sorting
*/
static compare(a, b) {
const pathCmp = a.path().localeCompare(b.path());
if (pathCmp !== 0)
return pathCmp;
const componentCmp = a.component().localeCompare(b.component());
if (componentCmp !== 0)
return componentCmp;
return a.bundleType() - b.bundleType();
}
}
exports.PathUtils = PathUtils;
//# sourceMappingURL=path.js.map