-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
496336e
commit a6e5dca
Showing
13 changed files
with
247 additions
and
237 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import {DestNode, SourceNode} from "../sync/sync-nodes"; | ||
|
||
export type SyncPair = [SourceNode, DestNode] | ||
|
||
export enum SourceCandidateType { | ||
MAPPED, | ||
MARKED, | ||
} | ||
|
||
export interface MappedSourceCandidate { | ||
type: SourceCandidateType.MAPPED; | ||
implicit?: boolean; | ||
node: SourceNode; | ||
} | ||
|
||
export interface MarkedSourceCandidate { | ||
type: SourceCandidateType.MARKED; | ||
implicit?: boolean; | ||
node: SourceNode; | ||
markerSpecificity: number; | ||
} | ||
|
||
export type SourceCandidate = MappedSourceCandidate | MarkedSourceCandidate; | ||
|
||
export type SourceCandidateGenerator = (sourceNode: SourceNode) => SourceCandidate |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,47 @@ | ||
import * as fs from "fs"; | ||
import {ScriptConfig } from "./models/script-config"; | ||
import {createDestTree, createSourceTree, SourceNode} from "./sync/sync-tree"; | ||
import {DestNode, SourceNode} from "./sync/sync-nodes"; | ||
import * as osPath from "path"; | ||
import {MATERIAL_ROOT, SCRIPTS_ROOT} from "../../config/builder-config"; | ||
import {synchronizeToScript} from "./sync/sync"; | ||
import {Logger} from "./logger"; | ||
import {Logger} from "./util/logger"; | ||
import {createDestTree, createSourceTree} from "./sync/sync-tree-builder"; | ||
import {copyFilesToScriptDir, removeObsoleteScriptFiles} from "./sync/file-ops"; | ||
import {applyMarkers, applySectionMappings, collectSyncPairs} from "./sync/sync-tree-processing"; | ||
|
||
export function buildScripts(scriptsConfigsFile: string) { | ||
const scriptsConfigs = loadScriptsConfigs(scriptsConfigsFile); | ||
const scriptsConfigs = _loadScriptsConfigs(scriptsConfigsFile); | ||
|
||
const materialTree = createMaterialTree(); | ||
const materialTree = _createMaterialTree(); | ||
Object.entries(scriptsConfigs).forEach(([scriptRoot, scriptConfig]: [string, ScriptConfig]) => { | ||
buildScript(scriptRoot, scriptConfig, materialTree); | ||
_buildScript(scriptRoot, scriptConfig, materialTree); | ||
}); | ||
return Object.keys(scriptsConfigs); | ||
} | ||
|
||
function createMaterialTree(): SourceNode { | ||
const materialRootPath = osPath.resolve(MATERIAL_ROOT); | ||
return createSourceTree(materialRootPath); | ||
} | ||
|
||
function loadScriptsConfigs(scriptsConfigsName: string) { | ||
function _loadScriptsConfigs(scriptsConfigsName: string) { | ||
const scriptsConfigsPath = `config/scriptsConfigs/${scriptsConfigsName}`; | ||
if (!fs.existsSync(scriptsConfigsPath)) { | ||
throw `No such scriptsConfigs file: ${scriptsConfigsPath}`; | ||
} | ||
return JSON.parse(fs.readFileSync(scriptsConfigsPath).toString()); | ||
} | ||
|
||
function buildScript(scriptRoot: string, scriptConfig: ScriptConfig, materialTree: SourceNode) { | ||
function _createMaterialTree(): SourceNode { | ||
const materialRootPath = osPath.resolve(MATERIAL_ROOT); | ||
return createSourceTree(materialRootPath); | ||
} | ||
|
||
function _buildScript(scriptRoot: string, scriptConfig: ScriptConfig, materialTree: SourceNode) { | ||
Logger.instance.info(`📝 Building script '${scriptRoot}'`); | ||
const scriptRootPath = osPath.resolve(osPath.join(SCRIPTS_ROOT, scriptRoot)); | ||
const scriptTree = createDestTree(scriptRootPath); | ||
synchronizeToScript(materialTree, scriptTree, scriptConfig); | ||
_synchronizeToScript(materialTree, scriptTree, scriptConfig); | ||
} | ||
|
||
function _synchronizeToScript(materialTree: SourceNode, scriptTree: DestNode, scriptConfig: ScriptConfig): void { | ||
applySectionMappings(scriptConfig, scriptTree, materialTree); | ||
applyMarkers(materialTree, scriptTree, scriptConfig.markers); | ||
const syncPairs = collectSyncPairs(scriptTree); | ||
copyFilesToScriptDir(syncPairs); | ||
removeObsoleteScriptFiles(scriptTree); | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import {DestNode, SyncNode} from "./sync-nodes"; | ||
import {Logger} from "../util/logger"; | ||
import osPath from "path"; | ||
import * as fs from "fs-extra"; | ||
|
||
export function copyFilesToScriptDir(syncPairs: [SyncNode, SyncNode][]): void { | ||
Logger.instance.info('🖨 Copying resources to script...') | ||
syncPairs.forEach(([sourceNode, destNode]) => { | ||
copyFileIfChanged(sourceNode.absPath, destNode.absPath); | ||
}) | ||
} | ||
|
||
function copyFileIfChanged(sourcePath: string, destPath: string): void { | ||
if (!fileHasChanged(sourcePath, destPath)) { | ||
return; | ||
} | ||
|
||
const destDir = osPath.dirname(destPath); | ||
if (!fs.existsSync(destDir)) { | ||
fs.mkdirSync(destDir, { recursive: true }); | ||
} | ||
|
||
Logger.instance.info(`[COPY] '${sourcePath}' -> '${destPath}'`); | ||
fs.copySync(sourcePath, destPath, { preserveTimestamps: true }); | ||
} | ||
|
||
function fileHasChanged(sourcePath: string, destPath: string): boolean { | ||
if (!fs.existsSync(destPath)) { | ||
return true; | ||
} | ||
|
||
const sourceStats = fs.statSync(sourcePath); | ||
const destStats = fs.statSync(destPath); | ||
return sourceStats.mtime.toUTCString() != destStats.mtime.toUTCString(); | ||
} | ||
|
||
export function removeObsoleteScriptFiles(scriptTree: DestNode): void { | ||
const deletionCandidates = scriptTree | ||
.collect((node: DestNode) => !node.hasUsableSourceCandidates()); | ||
if (deletionCandidates.length > 0) { | ||
Logger.instance.info('🗑️ Deleting obsolete script files...'); | ||
deletionCandidates.forEach(candidate => { | ||
deleteFile(candidate.absPath); | ||
}); | ||
} | ||
} | ||
|
||
function deleteFile(path: string): void { | ||
if (fs.existsSync(path)) { | ||
Logger.instance.info(`[DELETE] '${path}'`); | ||
fs.rmSync(path, {recursive: true}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import fs from "fs"; | ||
import osPath from "path"; | ||
import {DestNode, SourceNode, SyncNode} from "./sync-nodes"; | ||
|
||
export function createSourceTree(rootPath: string): SourceNode { | ||
const sourceRoot = new SourceNode(rootPath, []); | ||
_createDirTree(sourceRoot, rootPath); | ||
return sourceRoot; | ||
} | ||
|
||
export function createDestTree(rootPath: string): DestNode { | ||
const destRoot = new DestNode(rootPath); | ||
_createDirTree(destRoot, rootPath); | ||
return destRoot; | ||
} | ||
|
||
function _createDirTree(currentNode: SyncNode, currentAbsPath: string): void { | ||
if (!fs.existsSync(currentAbsPath)) { | ||
return; | ||
} | ||
fs.readdirSync(currentAbsPath).forEach(childPath => { | ||
const childAbsPath = osPath.join(currentAbsPath, childPath); | ||
if (fs.statSync(childAbsPath).isFile()) { | ||
currentNode.appendChild(childPath); | ||
} else { | ||
const childNode = currentNode.appendChild(childPath); | ||
_createDirTree(childNode, childAbsPath); | ||
} | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import {DestNode, SourceNode} from "./sync-nodes"; | ||
import {MarkersDefinition, ScriptConfig, SectionMapping} from "../models/script-config"; | ||
import * as osPath from 'path'; | ||
import {calculateSpecificity, canonicalNameFrom, hasApplicableMarkers} from "./markers"; | ||
import {MarkedSourceCandidate, SourceCandidate, SourceCandidateType, SyncPair} from "../models/sync"; | ||
|
||
export function applySectionMappings(scriptConfig: ScriptConfig, scriptTree: DestNode, materialTree: SourceNode): void { | ||
scriptConfig.mappings.forEach(sectionMapping => { | ||
const sourceNode = _applySectionMapping(sectionMapping, scriptTree, materialTree); | ||
_applyIgnorePaths(sectionMapping, sourceNode); | ||
}); | ||
} | ||
|
||
function _applySectionMapping(sectionMapping: SectionMapping, scriptTree: DestNode, materialTree: SourceNode): SourceNode { | ||
const sourceSegments = _splitPathSegments(sectionMapping.material); | ||
const destSegments = _splitPathSegments(sectionMapping.section); | ||
|
||
const sourceNode = materialTree | ||
.findNode(sourceSegments) | ||
.expect(`Material tree does not have a node '${sectionMapping.material}'`) as SourceNode; | ||
const destNode = scriptTree.ensureNode(destSegments); | ||
|
||
sourceNode.propagateAsSourceCandidateFor(destNode, (node => { | ||
return { | ||
type: SourceCandidateType.MAPPED, | ||
node, | ||
} | ||
})); | ||
|
||
return sourceNode; | ||
} | ||
|
||
function _applyIgnorePaths(sectionMapping: SectionMapping, sourceNode: SourceNode) { | ||
if (!sectionMapping.ignore) { | ||
return; | ||
} | ||
|
||
sectionMapping.ignore.forEach(ignorePath => { | ||
sourceNode | ||
.findNode(_splitPathSegments(ignorePath)) | ||
.ifPresent((ignoredRootNode: SourceNode) => { | ||
ignoredRootNode.propagateAsIgnored(); | ||
}); | ||
}); | ||
} | ||
|
||
export function applyMarkers(sourceTree: SourceNode, destTree: DestNode, markersDefinition: MarkersDefinition) { | ||
sourceTree | ||
.collect((sourceNode: SourceNode) => sourceNode.isMarked) | ||
.filter((markedNode: SourceNode) => hasApplicableMarkers(markedNode, markersDefinition)) | ||
.forEach((markedNode: SourceNode) => { | ||
const specificity = calculateSpecificity(markedNode, markersDefinition); | ||
const canonicalPathSegments = _splitPathSegments(markedNode.treePath) | ||
.map(segment => canonicalNameFrom(segment)); | ||
const destNode = destTree.ensureNode(canonicalPathSegments); | ||
|
||
markedNode.propagateAsSourceCandidateFor(destNode, (sourceNode: SourceNode) => { | ||
return { | ||
type: SourceCandidateType.MARKED, | ||
node: sourceNode, | ||
markerSpecificity: specificity | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
export function collectSyncPairs(processedScriptTree: DestNode): SyncPair[] { | ||
return (processedScriptTree | ||
.collect(node => node.isLeaf()) as DestNode[]) | ||
.filter(leaf => leaf.hasUsableSourceCandidates()) | ||
.map(destNode => { | ||
return [_determineBestSourceCandidate(destNode.sourceCandidates), destNode]; | ||
}); | ||
} | ||
|
||
function _determineBestSourceCandidate(candidates: SourceCandidate[]): SourceNode { | ||
const sortMarkedCandidatesBySpecificity = (a: MarkedSourceCandidate, b: MarkedSourceCandidate) => { | ||
return a.markerSpecificity - b.markerSpecificity; | ||
}; | ||
|
||
const mapped = candidates | ||
.filter(candidate => candidate.type == SourceCandidateType.MAPPED); | ||
const mappedExplicit = mapped.filter(candidate => !candidate.implicit); | ||
const mappedImplicit = mapped.filter(candidate => candidate.implicit); | ||
|
||
const marked = candidates | ||
.filter(candidate => candidate.type == SourceCandidateType.MARKED); | ||
const markedExplicit = marked | ||
.filter(candidate => !candidate.implicit) | ||
.sort(sortMarkedCandidatesBySpecificity) | ||
const markedImplicit = marked | ||
.filter(candidate => candidate.implicit) | ||
.sort(sortMarkedCandidatesBySpecificity) | ||
|
||
const sortedCandidates = [ | ||
...mappedExplicit, | ||
...markedExplicit, | ||
...mappedImplicit, | ||
...markedImplicit | ||
]; | ||
|
||
return sortedCandidates[0].node; | ||
} | ||
|
||
function _splitPathSegments(path: string): string[] { | ||
return path.split(osPath.sep).filter(segment => !!segment && segment != '.'); | ||
} |
Oops, something went wrong.