adminslog/tools/collector/link_collector.js

189 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
class ScriptInfo {
static dir() {
return path.dirname(__filename);
}
static cwd() {
return process.cwd();
}
}
class ConfigLoader {
static load(filename = 'config.jsonc') {
const configPath = path.join(ScriptInfo.dir(), filename);
if (!fs.existsSync(configPath)) {
console.error(` ⚠ Konfigurationsdatei nicht gefunden: ${configPath}`);
process.exit(1);
}
const raw = fs.readFileSync(configPath, 'utf8');
const clean = raw
.split('\n')
.filter(line => !line.trim().startsWith('//'))
.join('');
return JSON.parse(clean);
}
}
class ArgParser {
static parse() {
const argv = process.argv.slice(2);
const result = { scan: null, ignore: [], reset: false, hilfe: false };
while (argv.length) {
const arg = argv.shift();
if (!arg)
break;
if (arg === '-h' || arg === '--hilfe') {
result.hilfe = true;
}
else if (arg === '--reset') {
result.reset = true;
}
else if ((arg === '-s' || arg === '--scan') && argv.length) {
result.scan = argv.shift().split(',').map(x => x.trim()).filter(Boolean);
}
else if ((arg === '-x' || arg === '--ignore') && argv.length) {
result.ignore = argv.shift().split(',').map(x => x.trim()).filter(Boolean);
}
else {
console.warn(` ⚠ Unbekannter Parameter: ${arg}`);
result.hilfe = true;
break;
}
}
return result;
}
static showHelp() {
console.log(`
(C) 2025 - Adam Skotarczak (ionivation.com)
🛈 Tool das Verzeichnisse nach Markdown-Dateien durchsucht und diese an eine definierte Liste anhängt als Markdown-Link.
Verwendung: ts-node link_collector.ts [OPTIONEN]
-s, --scan Kommagetrennte Liste von Verzeichnissen zum Durchsuchen (relativ zum Aufrufpfad)
-x, --ignore Kommagetrennte Liste von Verzeichnissen, die ignoriert werden sollen
--reset Löscht das Logfile 'processed.log' und beendet sich
-h, --hilfe Zeigt diese Hilfe
Beispiel:
ts-node link_collector.ts -s docs,notes -x "docs/alt"
`);
}
}
class FileScanner {
static *find(rootDirs, ignoreDirs, extensions) {
for (const root of rootDirs) {
const absRoot = path.resolve(root);
const stack = [absRoot];
while (stack.length) {
const current = stack.pop();
const rel = path.relative(ScriptInfo.cwd(), current).replace(/\\/g, '/');
if (ignoreDirs.some(ignored => rel.startsWith(ignored)))
continue;
const entries = fs.readdirSync(current, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(current, entry.name);
if (entry.isDirectory()) {
stack.push(fullPath);
}
else if (extensions.some(ext => entry.name.endsWith(ext))) {
yield fullPath;
}
}
}
}
}
}
class TitleExtractor {
static extract(filepath) {
try {
const content = fs.readFileSync(filepath, 'utf8');
for (const line of content.split('\n')) {
if (line.trim().startsWith('#')) {
return line.replace(/^#+/, '').trim();
}
}
}
catch (e) {
console.error(`⚠ Fehler beim Lesen von ${filepath}: ${e}`);
}
return path.basename(filepath, path.extname(filepath));
}
}
class ProcessedLog {
constructor(filename) {
this.logPath = path.join(ScriptInfo.dir(), filename);
this.entries = new Set();
this.load();
}
load() {
if (!fs.existsSync(this.logPath))
return;
const lines = fs.readFileSync(this.logPath, 'utf8').split('\n').map(x => x.trim()).filter(Boolean);
this.entries = new Set(lines.map(p => p.replace(/\\/g, '/')));
}
has(posixPath) {
return this.entries.has(posixPath);
}
update(newPaths) {
const content = newPaths.map(p => p.replace(/\\/g, '/')).join('\n') + '\n';
fs.appendFileSync(this.logPath, content, 'utf8');
}
reset() {
if (fs.existsSync(this.logPath)) {
fs.unlinkSync(this.logPath);
console.log('🧹 Logfile gelöscht:', this.logPath);
}
else {
console.log(' Logfile existierte nicht:', this.logPath);
}
}
}
class MarkdownAppender {
static append(outputFile, links) {
const content = links.join('\n') + '\n';
fs.appendFileSync(outputFile, content, 'utf8');
}
}
// Einstiegspunkt
function main() {
const config = ConfigLoader.load();
const args = ArgParser.parse();
if (args.hilfe) {
ArgParser.showHelp();
return;
}
const log = new ProcessedLog(config.processed_log || 'processed.log');
if (args.reset) {
log.reset();
return;
}
const cwd = ScriptInfo.cwd();
const outputFile = path.resolve(cwd, config.output_file || 'output.md');
const rootDirs = (args.scan || config.root_dirs || []).map(d => path.resolve(d));
const ignoreDirs = (args.ignore || []).map(d => path.relative(cwd, path.resolve(d)).replace(/\\/g, '/'));
const extensions = config.extensions || ['.md'];
const newLinks = [];
const newProcessed = [];
for (const absPath of FileScanner.find(rootDirs, ignoreDirs, extensions)) {
const relPath = path.relative(cwd, absPath).replace(/\\/g, '/');
if (path.resolve(absPath) === outputFile || log.has(relPath))
continue;
const title = TitleExtractor.extract(absPath);
newLinks.push(`- [${title}](${relPath})`);
newProcessed.push(relPath);
}
if (newLinks.length > 0) {
MarkdownAppender.append(outputFile, newLinks);
log.update(newProcessed);
console.log(`${newLinks.length} neue Links hinzugefügt.`);
}
else {
console.log(' Keine neuen Dateien gefunden.');
}
}
main();