[Scripts] Add a script to automatically rename files
This script automatically fixes any file name errors to ensure all files are named correctly. Use with caution, as I have not yet tested it much
This commit is contained in:
		
							
								
								
									
										237
									
								
								scripts/util/auto-renamer/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								scripts/util/auto-renamer/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,237 @@ | ||||
| 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 {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, 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 ) ); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user