const fs = require('fs'); const { execSync, spawn } = require('child_process'); function create(file, fileName, emoji) { const startTime = Date.now(); let data = fs.readFileSync(file, 'utf8'); const emojis = new Set(["🪟", "🍎", "🐧", "🟢", "⭐", "🤖"]); let result = []; let lineCount = 0; const lines = data.split('\n'); const outputDir = './filter'; if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir); } for (let i = 0; i < lines.length; i++) { let line = lines[i]; if (lineCount < 20) { result.push(line); lineCount++; continue; } line = line.replace(/ /g, ''); if ([...emojis].some(emojiChar => line.includes(emojiChar)) && !line.includes(emoji)) { continue; } result.push(lines[i]); } fs.writeFileSync(`${outputDir}/${fileName}.md`, result.join('\n').replaceAll('./filter/', '').replaceAll('href="./', 'href="../').replaceAll('src="./', 'src="../')); console.log(`${fileName} time: \x1b[32m${Date.now() - startTime}ms\x1b[0m`); } function categorize() { create("README.md", "windows-only", "🪟"); create("README.md","macOS-only", "🍎"); create("README.md","linux-only", "🐧"); create("README.md","open-source-only", "🟢"); create("README.md","recommended-only", "⭐"); create("MOBILE.md", "android-only", "🤖"); create("MOBILE.md","iOS-only", "🍎"); create("MOBILE.md","open-source-mobile-only", "🟢"); create("MOBILE.md","recommended-mobile-only", "⭐"); } function format(file = "README.md") { const data = fs.readFileSync(file, 'utf8'); const updatedData = data .split('\n') .map(line => { // line = line.replace(/https:\/\/www\./g, 'https://'); // removes www. (DON'T USE! Some links break!) line = line.replace(/\((https?:\/\/.*?)\/\)/g, "($1)"); // removes trailing / // if (line.startsWith('- [')) { // const parts = line.split(' - '); // if (parts.length >= 2) { // let description = parts[1]; // description = description.replace(/^A\s+/i, '').replace(/^An\s+/i, ''); // description = description.charAt(0).toUpperCase() + description.slice(1); // parts[1] = description; // line = parts.join(' - '); // } // } return line; }) .join('\n'); fs.writeFileSync(file, updatedData, 'utf8'); console.log(`\x1b[32m${file} has been formatted.\x1b[0m`); } /** * @returns {Array} list of links in a given file */ function getLinks(file = "README.md") { const data = fs.readFileSync(file, "utf8"); const links = data.match(/\[.*?\]\(https?:\/\/.*?\)/g) || []; return links.map((url) => url.split("(").at(-1).slice(0, -1)); } /** Print the amount of links in a given file */ function countLinks(file = "README.md") { const linkCount = getLinks(file).length; console.log(`Total links in ${file}: \x1b[32m${linkCount}\x1b[0m`); } /** * DEPENDENCY: curl * @param {any} url string * @returns {Promise} test if a given url is reachable in 3 seconds */ function testUrl(url) { return new Promise((resolve, reject) => { const script = spawn("curl", ["-Is", "--connect-timeout", "3", url]); let output = ""; let errorOutput = ""; script.stdout.on("data", (data) => { output += data.toString(); }); script.stderr.on("data", (data) => { errorOutput += data.toString(); }); script.on("close", (code) => { if (code === 0) { // console.log(url, "is valid"); resolve(output); } else { console.log(url, "is not reachable"); reject(errorOutput); } }); script.on("error", (err) => { console.log(url, "failed to execute"); reject(err); }); }); } /** * Test if all links in a given file are valid (reachable) * @param {string} file * @returns {Promise} */ function testLinksReachable(file = "README.md") { console.log(`Testing links in \x1b[32m${file}\x1b[0m`); const promises = getLinks(file).map(async (url) => { try { await testUrl(url); } catch (_) {} }); return Promise.all(promises); } async function testLinksInAllFiles() { try { await testLinksReachable("README.md"); await testLinksReachable("MOBILE.md"); } catch (error) { console.error("An error occurred:", error); } } function fastGit(message = "update") { try { execSync('git add -A', { stdio: 'inherit' }); execSync(`git commit -m "${message}"`, { stdio: 'inherit' }); execSync('git push', { stdio: 'inherit' }); console.log('Changes have been committed and pushed.'); } catch (error) { console.error('Error running git commands:', error); } } function runAll() { countLinks("README.md"); countLinks("MOBILE.md"); // formatFiles(); createToC(); categorize(); countLinks("README.md"); countLinks("MOBILE.md"); } function analyze(file = "README.md") { const data = fs.readFileSync(file, 'utf8'); const words = data.match(/\b\w+\b/g); const wordCount = words ? words.length : 0; const linkCount = (data.match(/\[.*?\]\(https?:\/\/.*?\)/g) || []).length; const characterCount = data.length; console.log(`${file} Word Count: \x1b[32m${wordCount}\x1b[0m`); console.log(`${file} Character Count: \x1b[32m${characterCount}\x1b[0m`); console.log(`${file} Link Count: \x1b[32m${linkCount}\x1b[0m`); } function generateToc(filePath) { const content = fs.readFileSync(filePath, 'utf-8'); const lines = content.split('\n'); let toc = []; lines.forEach(line => { const match = /^#{2,6}\s+(.+)/.exec(line); if (match) { const level = match[0].indexOf(' ') - 1; const title = match[1].trim(); const anchor = title .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+/, '') .replace(/-+$/, ''); toc.push(`${' '.repeat(level - 1)}- [${title}](#${anchor})`); } }); const tocContent = toc.slice(1).join('\n'); return tocContent; } function findAF(file, name, message, suffix) { const data = fs.readFileSync(file, 'utf8'); const startMarker = ``.trim(); const startIndex = data.indexOf(startMarker); if (startIndex !== -1) { const cleanStartIndex = data.indexOf(">", startIndex) + 1; const endIndex = data.indexOf(endMarker, cleanStartIndex); if (endIndex !== -1) { let newData = data.slice(0, cleanStartIndex) + message + data.slice(endIndex); if (suffix) { const newStartMarker = ``; newData = newData.replace(data.slice(startIndex, cleanStartIndex), newStartMarker); } fs.writeFileSync(file, newData, 'utf8'); console.log(file + " updated successfully."); } else { console.log(`No end marker found for ${startMarker}`); } } else { console.log(`No content found for ${startMarker}`); } } function createToC() { findAF("README.md", "TOC", "\n\n" + generateToc("README.md") + "\n\n", `: ${new Date().toLocaleString('en-US', { timeZoneName: 'short', weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true })}`); findAF("MOBILE.md", "TOC", "\n\n" + generateToc("MOBILE.md") + "\n\n", `: ${new Date().toLocaleString('en-US', { timeZoneName: 'short', weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true })}`); } function formatFiles() { format("README.md"); format("MOBILE.md"); } const args = process.argv.slice(2); if (args.includes('--analyze')) { analyze("README.md"); analyze("MOBILE.md"); } else if (args.includes('--toc')) { createToC(); } else if (args.includes('--categorize')) { categorize(); } else if (args.includes('--format')) { formatFiles(); } else if (args.includes('--links')) { countLinks("README.md"); countLinks("MOBILE.md"); } else if (args.includes('--fastgit')) { const commitMessage = args.slice(1).join(' '); if (commitMessage) { fastGit(commitMessage); } else { console.log('Please provide a commit message after --fastgit'); } } else if (args.includes('--all')) { runAll(); } else if (args.includes('--test-links')) { testLinksInAllFiles() } else { console.log("Usage:"); console.log(" node index.js --categorize Categorize based on icons"); console.log(" node index.js --format Format README.md"); console.log(" node index.js --links Count and display total links in README.md"); console.log(" node index.js --fastgit Run git commands with the specified commit message"); console.log(" node index.js --analyze Print some info about README.md"); console.log(" node index.js --toc Update the table of contents"); console.log(" node index.js --all Run all the commands (format, categorize, links)"); }