mirror of
https://github.com/OrcaSlicer/OrcaSlicer_WIKI.git
synced 2026-05-17 00:25:45 +03:00
228 lines
9.8 KiB
YAML
228 lines
9.8 KiB
YAML
name: Find Unreferenced Images
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- '**/*.md'
|
|
- '**/*.markdown'
|
|
- '**/*.mdown'
|
|
- '**/*.mkd'
|
|
- '**/*.mkdn'
|
|
- '**/*.mdx'
|
|
workflow_dispatch: {}
|
|
|
|
jobs:
|
|
unreferenced-images:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
env:
|
|
ERROR_BLOCK: ''
|
|
RANKING_BLOCK: ''
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Find unreferenced images in /images
|
|
id: find_images
|
|
uses: actions/github-script@v9
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
with:
|
|
script: |
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const workspace = process.cwd();
|
|
const workspaceRoot = path.resolve(workspace);
|
|
const currentRepo = context.repo.repo;
|
|
const currentOwner = context.repo.owner;
|
|
|
|
const allowedImageExt = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.bmp', '.ico', '.avif']);
|
|
const allowedMarkdownExt = new Set(['.md', '.markdown', '.mdown', '.mkd', '.mkdn', '.mdx']);
|
|
|
|
function collectFilesUnder(relativeDir, extSet) {
|
|
const files = [];
|
|
const absoluteDir = relativeDir ? path.join(workspaceRoot, relativeDir) : workspaceRoot;
|
|
let entries;
|
|
try {
|
|
entries = fs.readdirSync(absoluteDir, { withFileTypes: true });
|
|
} catch (_) {
|
|
return files;
|
|
}
|
|
for (const entry of entries) {
|
|
if (entry.name === '.git') continue;
|
|
if (relativeDir === 'images' && entry.isDirectory() && entry.name === 'misc') continue;
|
|
const rel = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
|
|
if (entry.isDirectory()) {
|
|
files.push(...collectFilesUnder(rel, extSet));
|
|
} else if (entry.isFile()) {
|
|
const ext = path.extname(entry.name).toLowerCase();
|
|
if (extSet.has(ext)) files.push(rel.replace(/\\/g, '/'));
|
|
}
|
|
}
|
|
return files;
|
|
}
|
|
|
|
function lineFromIndex(text, index) {
|
|
let line = 1;
|
|
for (let i = 0; i < index; i += 1) {
|
|
if (text.charCodeAt(i) === 10) line += 1;
|
|
}
|
|
return line;
|
|
}
|
|
|
|
// Gather images under images/ folder
|
|
const candidateImages = collectFilesUnder('images', allowedImageExt).map(p => p.replace(/\\/g, '/'));
|
|
if (!candidateImages.length) {
|
|
core.info('No images found under images/; skipping unreferenced image check.');
|
|
return;
|
|
}
|
|
|
|
// Build a map of image -> count
|
|
const counts = new Map();
|
|
for (const img of candidateImages) counts.set(img, 0);
|
|
|
|
// Gather markdown files to scan
|
|
const markdownFiles = collectFilesUnder('', allowedMarkdownExt);
|
|
if (!markdownFiles.length) {
|
|
core.info('No Markdown files found; skipping references scan.');
|
|
}
|
|
|
|
const codeBlockPattern = /^```+([\s\S]*?)^```+$/gm;
|
|
const markdownImagePattern = /!\[(?:[^\]]*)\]\(\s*([^\)\s]+)(?:\s+"[^"]*")?\s*\)/g;
|
|
const htmlImagePattern = /<img\b[^>]*>/gi;
|
|
|
|
function parseGithubRawLink(rawUrl) {
|
|
let parsed;
|
|
try { parsed = new URL(rawUrl); } catch (_) { return null; }
|
|
const hostname = parsed.hostname.toLowerCase();
|
|
if (hostname === 'raw.githubusercontent.com') {
|
|
const parts = parsed.pathname.split('/').filter(Boolean);
|
|
if (parts.length < 3) return null;
|
|
const owner = parts[0];
|
|
const repo = parts[1];
|
|
const ref = decodeURIComponent(parts[2]);
|
|
const rel = parts.slice(3).map(decodeURIComponent).join('/');
|
|
return { owner, repo, ref, path: rel };
|
|
}
|
|
if (hostname === 'github.com') {
|
|
const parts = parsed.pathname.split('/').filter(Boolean);
|
|
if (parts.length < 5) return null;
|
|
const owner = parts[0];
|
|
const repo = parts[1];
|
|
const blobOrRaw = parts[2];
|
|
if (!['raw', 'blob'].includes(blobOrRaw)) return null;
|
|
const ref = decodeURIComponent(parts[3]);
|
|
const rel = parts.slice(4).map(decodeURIComponent).join('/');
|
|
return { owner, repo, ref, path: rel };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function normalizeLocalPath(sourceFile, raw) {
|
|
if (!raw) return null;
|
|
let t = raw.trim();
|
|
if (t.startsWith('<') && t.endsWith('>')) t = t.slice(1, -1).trim();
|
|
// drop query string
|
|
const q = t.indexOf('?'); if (q !== -1) t = t.slice(0, q);
|
|
// absolute repo path
|
|
if (t.startsWith('/')) {
|
|
const rel = t.slice(1).replace(/\\/g, '/');
|
|
return rel;
|
|
}
|
|
// relative paths from source file
|
|
const candidate = path.normalize(path.join(path.dirname(sourceFile), t));
|
|
const relToRoot = path.relative(workspaceRoot, path.resolve(workspaceRoot, candidate)).replace(/\\/g, '/');
|
|
return relToRoot;
|
|
}
|
|
|
|
// Iterate markdown files and accumulate counts
|
|
for (const file of markdownFiles) {
|
|
const absolute = path.join(workspaceRoot, file);
|
|
let text = fs.readFileSync(absolute, 'utf8');
|
|
const textWithoutCodeBlocks = text.replace(codeBlockPattern, '');
|
|
|
|
// Markdown-style images: 
|
|
markdownImagePattern.lastIndex = 0;
|
|
let m;
|
|
while ((m = markdownImagePattern.exec(textWithoutCodeBlocks)) !== null) {
|
|
const url = m[1];
|
|
if (!url) continue;
|
|
// If url is a raw github link to this repo, parse it
|
|
const repoPath = parseGithubRawLink(url);
|
|
if (repoPath && repoPath.owner === currentOwner && repoPath.repo === currentRepo) {
|
|
const normalized = repoPath.path.replace(/\\/g, '/');
|
|
if (counts.has(normalized)) counts.set(normalized, counts.get(normalized) + 1);
|
|
continue;
|
|
}
|
|
// Local path
|
|
const local = normalizeLocalPath(file, url);
|
|
if (!local) continue;
|
|
// Try the path directly
|
|
if (counts.has(local)) counts.set(local, counts.get(local) + 1);
|
|
else {
|
|
// Fallback: match by basename only when unique to avoid ambiguous counting
|
|
const base = path.basename(local).toLowerCase();
|
|
const matches = Array.from(counts.keys()).filter(img => path.basename(img).toLowerCase() === base);
|
|
if (matches.length === 1) counts.set(matches[0], counts.get(matches[0]) + 1);
|
|
}
|
|
}
|
|
|
|
// <img src="..."> parsing
|
|
htmlImagePattern.lastIndex = 0;
|
|
while ((m = htmlImagePattern.exec(textWithoutCodeBlocks)) !== null) {
|
|
const tag = m[0];
|
|
// extract src
|
|
const attrPattern = /src\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i;
|
|
const match = attrPattern.exec(tag);
|
|
const url = match && (match[1] || match[2] || match[3]) ? (match[1] || match[2] || match[3]) : null;
|
|
if (!url) continue;
|
|
const repoPath = parseGithubRawLink(url);
|
|
if (repoPath && repoPath.owner === currentOwner && repoPath.repo === currentRepo) {
|
|
const normalized = repoPath.path.replace(/\\/g, '/');
|
|
if (counts.has(normalized)) counts.set(normalized, counts.get(normalized) + 1);
|
|
continue;
|
|
}
|
|
const local = normalizeLocalPath(file, url);
|
|
if (!local) continue;
|
|
if (counts.has(local)) counts.set(local, counts.get(local) + 1);
|
|
else {
|
|
const base = path.basename(local).toLowerCase();
|
|
const matches = Array.from(counts.keys()).filter(img => path.basename(img).toLowerCase() === base);
|
|
if (matches.length === 1) counts.set(matches[0], counts.get(matches[0]) + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generate ranking of images by count
|
|
const ranking = [];
|
|
for (const [img, cnt] of counts) ranking.push({ img, cnt });
|
|
ranking.sort((a, b) => b.cnt - a.cnt || a.img.localeCompare(b.img));
|
|
const rankingLines = ranking.map(r => `${r.img}, ${r.cnt}`);
|
|
core.exportVariable('RANKING_BLOCK', rankingLines.join('\n'));
|
|
|
|
// Find images with 0 references
|
|
const unreferenced = ranking.filter(r => r.cnt === 0).map(r => r.img);
|
|
if (unreferenced.length) {
|
|
core.exportVariable('ERROR_BLOCK', unreferenced.join('\n'));
|
|
core.info(`Found ${unreferenced.length} unreferenced image(s) in images/`);
|
|
return;
|
|
}
|
|
core.exportVariable('ERROR_BLOCK', '');
|
|
core.info('No unreferenced images found in images/.');
|
|
|
|
- name: Show image ranking
|
|
run: |
|
|
echo 'Image ranking (image path, number of references):'
|
|
printf '```\n%s\n```\n' "$RANKING_BLOCK"
|
|
|
|
- name: Show unreferenced images
|
|
if: env.ERROR_BLOCK != ''
|
|
run: |
|
|
echo 'Unreferenced images under images/ (image path):'
|
|
printf '```\n%s\n```\n' "$ERROR_BLOCK"
|
|
exit 1
|