239 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			239 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const fs = require('fs');
 | |
| const path = require('path');
 | |
| const { argv } = require('process');
 | |
| 
 | |
| const config = {
 | |
|     // replace character of key with value, any character, apart from ['.', '_', '-', ' ']
 | |
|     'replace': {
 | |
|         'ä': 'ae',
 | |
|         'ö': 'oe',
 | |
|         'ü': 'ue',
 | |
|         ',': '-',
 | |
|         '&': 'And',
 | |
|     },
 | |
|     'rules': {
 | |
|         'underscore-before-and-after-number': true, // Will not do trailing or leading for filename
 | |
|         'enforce-leading-zero': true, // adds a leading zero to any number below 10
 | |
|         'camel-case-rules': {
 | |
|             'enforce-snake-case-for-filetypes': ['py'], // using underscores
 | |
|             'enforce-kebab-case-for-filetypes': ['css', 'html', 'scss', 'tex'], // using hyphens
 | |
|         },
 | |
|         'file-start-letter': 'lower', // lower, upper, unchanged
 | |
|         'directory-start-letter': 'upper', // lower, upper, unchanged
 | |
|         'replace-dots-with': '', // will not replace as leading character to not break dotfiles
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Recursively find all files with extension in a directory
 | |
|  * @param {string} dir The directory to search. Either absolute or relative path
 | |
|  * @param {string} extension The file extension to look for
 | |
|  * @param {boolean} cleanup If helper files (like .DS_STORE and Windows helpers should be auto-deleted)
 | |
|  * @param {string[]} ignoreList A list of filenames or directories to ignore
 | |
|  * @returns {{ files: string, directories: string }} returns a list of html files with their full path
 | |
|  */
 | |
| const treeWalker = (dir, extension, cleanup, ignoreList) => {
 | |
|     const ls = fs.readdirSync(dir);
 | |
|     const fileList = [];
 | |
|     const dirList = [];
 | |
|     for (let file in ls) {
 | |
|         if (fs.statSync(path.join(dir, ls[file])).isDirectory()) {
 | |
|             // Filter ignored directories
 | |
|             if (ignoreList === undefined || !ignoreList.includes(ls[file])) {
 | |
|                 const newData = treeWalker(path.join(dir, ls[file]), extension, ignoreList);
 | |
|                 const newFiles = newData.files;
 | |
|                 dirList.push( path.join( dir, ls[ file ] ) );
 | |
|                 for (let dir = 0; dir < newData.directories.length; dir++) {
 | |
|                     dirList.push( newData.directories[dir] );
 | |
|                 }
 | |
|                 for (let file = 0; file < newFiles.length; file++) {
 | |
|                     fileList.push(newFiles[file]);
 | |
|                 }
 | |
|             }
 | |
|         } else if (extension == '*' || ls[file].includes(extension)) {
 | |
|             if (ignoreList === undefined || !ignoreList.includes(ls[file])) {
 | |
|                 fileList.push(path.join(dir, ls[file]));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return { 'files': fileList, 'directories': dirList };
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @param {string} filename The filename to fix according to the rules
 | |
|  * @returns {string} the fixed filename
 | |
|  */
 | |
| const fixName = ( fn, ft ) => {
 | |
|     let out = '';
 | |
|     const enforceSnake = config.rules['camel-case-rules']['enforce-snake-case-for-filetypes'].includes( ft );
 | |
|     const enforceKebab = config.rules['camel-case-rules']['enforce-kebab-case-for-filetypes'].includes( ft );
 | |
|     const isDir = ft === 'directory';
 | |
|     const startLetter = isDir ? config.rules['directory-start-letter'] : config.rules['file-start-letter'];
 | |
|     let nextUpperCase = false;
 | |
| 
 | |
|     for ( let i = 0; i < fn.length; i++ ) {
 | |
|         const c = fn[i];
 | |
|         
 | |
|         if ( c == '.' ) {
 | |
|             // Rule: Removed after number, allowed elsewhere
 | |
|             if ( i > 0 && /[0-9]/.test( fn[ i - 1 ] ) ) {
 | |
|                 out += config.rules[ 'replace-dots-with' ];
 | |
|             } else {
 | |
|                 out += '.';
 | |
|             }
 | |
|         } else if ( /[A-Z]/.test( c ) ) {
 | |
|             // If we reach a capital letter and enforce either kebab-case or snake_case, we can assume that this is the start of a CamelCase word
 | |
|             if ( enforceKebab ) {
 | |
|                 out += '-' + c.toLowerCase();
 | |
|             } else if ( enforceSnake ) {
 | |
|                 out += '_' + c.toLowerCase();
 | |
|             } else {
 | |
|                 nextUpperCase = false;
 | |
|                 if ( i == 0 && startLetter === 'lower' ) {
 | |
|                     out += c.toLowerCase();
 | |
|                 } else {
 | |
|                     out += c;
 | |
|                 }
 | |
|             }
 | |
|         } else if ( c == ' ' ) {
 | |
|             // We always replace spaces, the question is just to what
 | |
|             if ( enforceKebab ) {
 | |
|                 out += '-';
 | |
|             } else if ( enforceSnake ) {
 | |
|                 out += '_';
 | |
|             } else {
 | |
|                 nextUpperCase = true;
 | |
|             }
 | |
|         } else if ( c == '_' ) {
 | |
|             // If we are not enforcing snake_case, then replace it
 | |
|             if ( !enforceSnake ) {
 | |
|                 if ( needsUnderscore( i, fn ) ) {
 | |
|                     out += '_';
 | |
|                 } else if ( enforceKebab ) {
 | |
|                     out += '-';
 | |
|                 } else {
 | |
|                     nextUpperCase = true;
 | |
|                 }
 | |
|             } else {
 | |
|                 out += '_'
 | |
|             }
 | |
|         } else if ( c == '-' ) {
 | |
|             // If we are not enforcing kebab-case
 | |
|             if ( !enforceKebab ) {
 | |
|                 if ( enforceSnake ) {
 | |
|                     out += '_';
 | |
|                 } else {
 | |
|                     nextUpperCase = true;
 | |
|                 }
 | |
|             } else {
 | |
|                 out += '-'
 | |
|             }
 | |
|         } else {
 | |
|             let curr = config.replace[ c ] === undefined ? c : config.replace[ c ];
 | |
|             if ( config.rules[ 'underscore-before-and-after-number' ] || config.rules['enforce-leading-zero'] ) {
 | |
|                 if ( /[0-9]/.test( c ) ) {
 | |
|                     if ( i < fn.length - 1 ) {
 | |
|                         if ( config.rules['enforce-leading-zero'] ) {
 | |
|                             const prevIsNumber = i > 0 && /[0-9]/.test( fn[i - 1] );
 | |
|                             const nextIsNumber = /[0-9]/.test( fn[i + 1] );
 | |
|                             if ( !nextIsNumber && ( i == 0|| !prevIsNumber ) ) {
 | |
|                                 curr = '0' + curr;
 | |
|                             }
 | |
|                         }
 | |
| 
 | |
|                         if ( config.rules['underscore-before-and-after-number'] ) {
 | |
|                             if ( !( /[0-9]/.test( fn[ i + 1 ] ) ) && fn[ i + 1 ] != '_' ) {
 | |
|                                 curr += '_';
 | |
|                             }
 | |
|                         }
 | |
|                     } else {
 | |
|                         if ( config.rules['enforce-leading-zero'] && ( i > 0 && !( /[0-9]/.test( fn[i - 1] ) ) ) ) {
 | |
|                             curr = '0' + curr;
 | |
|                         }
 | |
|                     }
 | |
|                 } else {
 | |
|                     if ( config.rules['underscore-before-and-after-number'] && /[0-9]/.test( fn[ i + 1 ] ) ) {
 | |
|                         curr += '_';
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             if ( nextUpperCase || ( i == 0 && startLetter === 'upper' ) ) {
 | |
|                 nextUpperCase = false;
 | |
|                 out += curr.toUpperCase();
 | |
|             } else {
 | |
|                 if ( i == 0 && startLetter === 'upper' ) {
 | |
|                     out += curr.toUpperCase();
 | |
|                 } else {
 | |
|                     out += curr.toLowerCase();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     return out;
 | |
| }
 | |
| 
 | |
| const needsUnderscore = ( i, fn ) => {
 | |
|     return ( i > 0 && /[0-9]/.test( fn[ i - 1 ] ) ) || ( i < fn - 1 && /[0-9]/.test( fn[ i + 1 ] ) )
 | |
| }
 | |
| 
 | |
| const separateDirAndFileAndFiletype = ( filename ) => {
 | |
|     const loc = filename.lastIndexOf( '/' ) + 1;
 | |
|     let ftl = filename.lastIndexOf( '.' ) + 1;
 | |
|     let fn = filename.substring( loc, ftl - 1 );
 | |
|     let ft = filename.substring( ftl );
 | |
|     if ( fs.statSync( filename ).isDirectory() ) {
 | |
|         ftl = filename.length;
 | |
|         fn = filename.substring( loc );
 | |
|         ft = 'directory';
 | |
|         
 | |
|     }
 | |
|     const dir = filename.slice( 0, loc - 1 );
 | |
|     return { 'filename': fn, 'dir': dir, 'filetype': ft };
 | |
| }
 | |
| 
 | |
| const fixDirName = ( directory, top ) => {
 | |
|     if ( directory === top ) {
 | |
|         return top;
 | |
|     }
 | |
| 
 | |
|     const f = separateDirAndFileAndFiletype( directory );
 | |
|     return fixDirName( f.dir, top ) + '/' + fixName( f.filename, f.filetype );
 | |
| }
 | |
| 
 | |
| if (argv[2] == '-h') {
 | |
|     console.log('auto-renamer [directory]\n\n=> Recursively rename files in directory');
 | |
| } else if (argv[2] == '-v') {
 | |
|     console.log('auto-renamer version 1.0.0, developed by Janis Hutz (development@janishutz.com)');
 | |
| } else {
 | |
|     // Recursively add all files in the directory
 | |
|     const fp = path.resolve( argv[2] );
 | |
|     const list = treeWalker(fp, '*', ['.git', '@girs']);
 | |
|     const files = list.files;
 | |
|     const directories = list.directories;
 | |
| 
 | |
|     for (let i = 0; i < files.length; i++) {
 | |
|         const file = files[i];
 | |
|         const f = separateDirAndFileAndFiletype( file );
 | |
|         let fixedFile = fixName( f.filename, f.filetype );
 | |
|         
 | |
|         // Rename
 | |
|         const fixedPath = f.dir + '/' + fixedFile + '.' + f.filetype;
 | |
|         console.log( file + ' -> ' + fixedPath );
 | |
|         fs.renameSync( file, fixedPath );
 | |
|     }
 | |
| 
 | |
|     // Fix directory names after file names. Sort array by decending length
 | |
|     directories.sort( ( a, b ) => {
 | |
|         return b.length - a.length;
 | |
|     } );
 | |
| 
 | |
|     // separate directories up until we reach the path of dir started in
 | |
|     for (let i = 0; i < directories.length; i++) {
 | |
|         const dir = directories[i];
 | |
|         const fixed = fixDirName( dir, fp );
 | |
|         console.log( dir + ' -> ' + fixed );
 | |
|         fs.renameSync( dir, fixDirName( dir, fp ) );
 | |
|     }
 | |
| }
 |