diff --git a/babel.config.cjs b/babel.config.js similarity index 100% rename from babel.config.cjs rename to babel.config.js diff --git a/conf/configs.js b/conf/configs.js deleted file mode 100644 index 0845f4e..0000000 --- a/conf/configs.js +++ /dev/null @@ -1,45 +0,0 @@ -import mainTemplates from './mainTemplates.mjs' -import constants from './folders.js' -import cv from './controlled_vocabulary.js' - -/** - * configuration générale à importer dans les utilitaires - */ -class config_rangement { - log_level = 'debug' // 'debug' | 'warn' |'info' - - version = '1.0.0' - iso_date_format = 'yyyy-MM-DDTHH.mm.ss' // target format for dates in file names - tagSeparator = ' ' - tagSectionSeparator = '--' - keepFreeText = true - keepTags = true - replaceUnderscoreWithSpaces = true - renameFolders = false - enableTestsLocally = false - reportStatistics = true - controlled_vocabulary = cv - constants = constants - base_archive_folder = constants.base_archive_folder - templates = mainTemplates; - statistics= {}; - keepOriginalNameInRename=true; - log_actions=true; - - /** - * override config if we want - * @param overridingConfig - */ - constructor (overridingConfig) { - let keys = Object.keys(overridingConfig) - let self = this; - keys.forEach(elem=>{ - if(self[key]){ - self[key] = elem - } - }) - } -} - -const rangement_instance = new config_rangement({}); -export default rangement_instance \ No newline at end of file diff --git a/conf/configs.ts b/conf/configs.ts new file mode 100644 index 0000000..8fd424c --- /dev/null +++ b/conf/configs.ts @@ -0,0 +1,61 @@ +import mainTemplates from './mainTemplates' +import constants from './folders' +import cv from './controlled_vocabulary' + + +import log, {LogLevelDesc} from 'loglevel' + +interface ConfigOverride { + iso_date_format?: string, + log_actions?: string, + log_level?: 'debug' | 'warn' | 'info', +} + +/** + * configuration générale à importer dans les utilitaires + */ +class config_rangement { + log_level: LogLevelDesc = 'debug' // 'debug' | 'warn' |'info' + + version = '1.0.0' + iso_date_format = 'yyyy-MM-DDTHH.mm.ss' // target format for dates in file names + tagSeparator = ' ' + tagSectionSeparator = '--' + keepFreeText = true + keepTags = true + replaceUnderscoreWithSpaces = true + renameFolders = false + enableTestsLocally = false + reportStatistics = true + controlled_vocabulary = cv + constants = constants + base_archive_folder = constants.base_archive_folder + templates = mainTemplates; + statistics = {}; + keepOriginalNameInRename = true; + log_actions = true; + + /** + * override config if we want + * @param overridingConfig + */ + constructor(overridingConfig?: ConfigOverride) { + + + log.setLevel('info') + if (overridingConfig) { + log.debug('configuration is overrided') + let keys = Object.keys(overridingConfig) + let self: any = this; + keys.forEach((elem, index) => { + if (self[keys[index]]) { + self[keys[index]] = elem + } + }) + } + + } +} + +const rangement_instance = new config_rangement({}); +export default rangement_instance diff --git a/conf/controlled_vocabulary.js b/conf/controlled_vocabulary.ts similarity index 100% rename from conf/controlled_vocabulary.js rename to conf/controlled_vocabulary.ts diff --git a/conf/folders.js b/conf/folders.ts similarity index 100% rename from conf/folders.js rename to conf/folders.ts diff --git a/conf/mainTemplates.mjs b/conf/mainTemplates.ts similarity index 67% rename from conf/mainTemplates.mjs rename to conf/mainTemplates.ts index 386be56..df2d00a 100644 --- a/conf/mainTemplates.mjs +++ b/conf/mainTemplates.ts @@ -1,7 +1,22 @@ -import constants from './folders.js' +import constants from './folders' +interface fileDetectionTemplate{ + 'example': string, + 'pattern': any, + 'auto_tag'?:string, + 'auto_sort_folder'?:string +} +interface AssemblyOfFileDetection { + downloaded_pic: fileDetectionTemplate, + telegram_pic: fileDetectionTemplate, + open_camera_default: fileDetectionTemplate, + open_camera_custom: fileDetectionTemplate, + screenshot: fileDetectionTemplate, + gopro_front: fileDetectionTemplate, + gopro_back : fileDetectionTemplate +} -export default { - 'downloaded_pic': { +const setOfFileDetections : AssemblyOfFileDetection= { + 'downloaded_pic':{ 'example': 'FyB8cZnWIAc21rw.jpg', 'pattern': /^\-\w{15}\.jpg/, 'auto_tag':'', @@ -43,4 +58,6 @@ export default { 'auto_tag':'gopro-back gopro', 'auto_sort_folder': constants.gopro_capture_folder }, -} \ No newline at end of file +} + +export default setOfFileDetections; diff --git a/development.sh b/development.sh index 2ef4de4..fe463e6 100644 --- a/development.sh +++ b/development.sh @@ -4,7 +4,7 @@ # this should work for dev purposes # alias should be set in you bash aliases files: # -# alias range="node /home/poule/encrypted/stockage-syncable/www/development/html/rangement/index.mjs --dry-run=true " +# alias range="ts-node /home/poule/encrypted/stockage-syncable/www/development/html/rangement/index.ts --dry-run=true " # ------------------------- range /home/poule/encrypted/stockage-syncable/www/development/html/rangement/testFiles/misnamed_file/2000-01-01T01.01.01\ FyB8cZnWIAc21rw\ --\ meme.jpg diff --git a/index.mjs b/index.mjs index 526dad1..96dc291 100644 --- a/index.mjs +++ b/index.mjs @@ -1,231 +1 @@ -/**--------------------- - * @name tykayn Rangement - * @description Rangement sorts and rename files depending on their exif data - * @contact contact@cipherbliss.com - --------------------- */ -/** --------------------- - libs - --------------------- */ -import fs from 'node-fs' -import minimist from 'minimist' -import log from 'loglevel' -import path from 'node:path' -/** --------------------- - custom utilities and configuration - --------------------- */ -import rangement_instance from './conf/configs.js' -import { - TestFindFormattedDate, - TestScreenShotIsFoundAndRenamed, - TestTagsAreDetectedInFileName -} from './utils/testFunctions.mjs' -import finder from './utils/finder.mjs' - -let mini_arguments - -log.setLevel(rangement_instance.log_level) -log.info(' ') - -function parseArguments () { - mini_arguments = minimist(process.argv.slice(2)) - log.debug('arguments', mini_arguments) -} - -/** - * if there is no original file name free text into the new name, append it to the free text part - * @param originalFileName - * @param fileMixedNewName - */ -function addOriginalFileNameIfMissing (originalFileName, fileMixedNewName) { - - if (!fileMixedNewName.includes(originalFileName)) { - let properties = finder.destructurateFileName(fileMixedNewName) - return properties.freeText + ' ' + originalFileName - } else { - return fileMixedNewName - } -} - -function renameFile (originalFileName, fileMixedNewName) { - if (rangement_instance.keepOriginalNameInRename) { - fileMixedNewName = addOriginalFileNameIfMissing(originalFileName, fileMixedNewName) - } - - fs.rename(originalFileName, fileMixedNewName, function (err) { - log.info('name changed', fileMixedNewName) - if (err) { - log.info('rename ERROR: ' + err) - } else { - // otherRenames.push(originalFileName) - otherRenames.push(fileMixedNewName) - - finder.statistics['filesModified']++ - } - }) -} - -export function makeFileNameFromProperties (fileProperties) { - - let tagPlace = '' - log.info('fileProperties.tags', fileProperties.tags) - if (fileProperties.tags.length && rangement_instance.keepTags) { - tagPlace = ' ' + rangement_instance.tagSectionSeparator + ' ' + fileProperties.tags.join(rangement_instance.tagSeparator) - } - console.log('fileProperties.dateStampExif', fileProperties.dateStampExif) - let newName = '' - + fileProperties.dateStampExif - + ' ' - + (rangement_instance.keepFreeText ? fileProperties.freeText : '') - + tagPlace - + fileProperties.extension - - if (rangement_instance.replaceUnderscoreWithSpaces) { - newName = newName.replace('_', ' ') - } - - newName = finder.cleanSpaces(newName) - - return newName -} - -let otherRenames = [] - -function shouldWeChangeName (structureForFile) { - log.info(' ______ shouldWeChangeName ', structureForFile.fileNameOriginal) - let newName = makeFileNameFromProperties(structureForFile) - log.debug('newName', newName) - if (structureForFile.fileNameOriginal !== newName && !otherRenames.includes(newName)) { - - log.info('\n ancien nom :', structureForFile.fileNameOriginal) - - log.info(' nouveau nom:', newName) - if (!mini_arguments['dry-run']) { - - renameFile(structureForFile.fullPath, structureForFile.folderPath + newName) - } else { - log.info('no renaming for real, this is a dry run') - finder.statistics['filesNotModified']++ - } - } else { - log.info(' rien à changer') - } -} - -/** - * guess file name on one file which is not a directory - * @param fullPath - */ -function guessFileNameOnOnefile (fullPath) { - - log.info('go guess file name on file: ', fullPath) - fs.stat(fullPath, (err, stats) => { - - if (err) { - log.error('échec fichier', err) - log.error('ce fichier n existe pas: ', fullPath) - return - } else { - - let structureForFile = finder.destructurateFileName(fullPath) - - // examiner les infos exif de chaque fichier pour proposer un nouveau nom - if (!structureForFile.dateStampInFileNameOriginal) { - log.info(' le nom de fichier "' + structureForFile.freeText + '" ne contient pas de date formatée au début') - - finder.findExifCreationDate(structureForFile.fullPath) - .then(data => { - log.info(' ... chercher la date de création : "' + structureForFile.freeText + '"') - let foundDate = finder.findEarliestDateInExifData(data) - - log.info(' =>>>>>>> foundDate : ', foundDate) - if (foundDate) { - structureForFile.dateStampExif = foundDate - } else { - log.info('pas de date trouvée dans le nom') - } - shouldWeChangeName(structureForFile) - } - , - (error) => { - log.warn('/////////// Error in reading exif of file: ' + error.message) - return '' - }) - } - - } - }) -} - -let expandedFileList = [] -let cwd = path.dirname(process.cwd()) + '/' + path.basename(process.cwd()) -console.log('cwd', cwd) - -function guessFileNameOnAllFilesFromArguments () { - - // parcourir les fichiers - log.debug('liste des fichiers', mini_arguments._) - let fileList = mini_arguments._ - - // test file exists - fileList.forEach(fullPath => { - log.debug('file list element: ', fullPath) - // parcourir les dossiers - isFolderOrFile(`${fullPath}`) - } - ) - - log.info('expanded file list :', expandedFileList) - expandedFileList.forEach(filePath => guessFileNameOnOnefile(filePath)) - - if (rangement_instance.reportStatistics || mini_arguments.stats) { - finder.reportStatistics() - } - -} - -function readSubdirectories (baseDir) { - const newGlob = baseDir - fs.readdir(baseDir, (err, files) => { - if (err) throw err - - console.log('readSubdirectories - files', files) - files.forEach((subDirOrFile) => { - const newFullPath = cwd + '/' + subDirOrFile - - if (fs.existsSync(newFullPath)) { - const s = fs.statSync(newFullPath) - - if (s.isFile()) { - expandedFileList.push(cwd + '/' + subDirOrFile) - } - } - }) - }) -} - -function isFolderOrFile (fileName) { - const stat = fs.statSync(fileName) - - if (stat.isDirectory()) { - let fileList = readSubdirectories(fileName) - console.log('fileList in directory ', fileName, '\n', fileList) - if (fileList) { - expandedFileList.push(...fileList) - } - } else if (stat.isFile()) { - expandedFileList.push(fileName) - } -} - -parseArguments() - -guessFileNameOnAllFilesFromArguments() - -// run tests -if (rangement_instance.enableTestsLocally) { - - TestTagsAreDetectedInFileName() - TestFindFormattedDate() - TestScreenShotIsFoundAndRenamed() -} - +console.log('hello index.mjs') \ No newline at end of file diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..447b697 --- /dev/null +++ b/index.ts @@ -0,0 +1,33 @@ +/**--------------------- + * @name tykayn Rangement + * @description Rangement sorts and rename files depending on their exif data + * @contact contact@cipherbliss.com + --------------------- */ +/** --------------------- + libs + --------------------- */ +import fs from 'node-fs' +import minimist from 'minimist' +import log from 'loglevel' +import * as path from 'node:path' +/** --------------------- + custom utilities and configuration + --------------------- */ +import rangement_instance from './conf/configs' +import finder from './utils/finder' + +let mini_arguments + +log.setLevel(rangement_instance.log_level) +log.info(' ') + +function parseArguments () { + mini_arguments = minimist(process.argv.slice(2)) + log.debug('arguments', mini_arguments) +} + + +parseArguments() + + +console.log('hello ts', finder) \ No newline at end of file diff --git a/jest.config.ts b/jest.config.ts index d27a961..9a9366a 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -7,6 +7,7 @@ module.exports = { transform: { "^.+\\.jsx?$": "babel-jest", "^.+\\.mjs$": "babel-jest", + "^.+\\.ts$": "babel-jest", }, testPathIgnorePatterns: ["/build/", "/node_modules/"], moduleFileExtensions: ["js", "jsx", "mjs"] diff --git a/old_index.mjs b/old_index.mjs new file mode 100644 index 0000000..5ccaf3e --- /dev/null +++ b/old_index.mjs @@ -0,0 +1,217 @@ +/**--------------------- + * @name tykayn Rangement + * @description Rangement sorts and rename files depending on their exif data + * @contact contact@cipherbliss.com + --------------------- */ +/** --------------------- + libs + --------------------- */ +import fs from 'node-fs' +import minimist from 'minimist' +import log from 'loglevel' +import path from 'node:path' +/** --------------------- + custom utilities and configuration + --------------------- */ +import rangement_instance from './conf/configs.js' +import finder from './utils/finder.js' + +let mini_arguments + +log.setLevel(rangement_instance.log_level) +log.info(' ') + +function parseArguments () { + mini_arguments = minimist(process.argv.slice(2)) + log.debug('arguments', mini_arguments) +} + +/** + * if there is no original file name free text into the new name, append it to the free text part + * @param originalFileName + * @param fileMixedNewName + */ +function addOriginalFileNameIfMissing (originalFileName, fileMixedNewName) { + + if (!fileMixedNewName.includes(originalFileName)) { + let properties = finder.destructurateFileName(fileMixedNewName) + return properties.freeText + ' ' + originalFileName + } else { + return fileMixedNewName + } +} + +function renameFile (originalFileName, fileMixedNewName) { + if (rangement_instance.keepOriginalNameInRename) { + fileMixedNewName = addOriginalFileNameIfMissing(originalFileName, fileMixedNewName) + } + + fs.rename(originalFileName, fileMixedNewName, function (err) { + log.info('name changed', fileMixedNewName) + if (err) { + log.info('rename ERROR: ' + err) + } else { + // otherRenames.push(originalFileName) + otherRenames.push(fileMixedNewName) + + finder.statistics['filesModified']++ + } + }) +} + +export function makeFileNameFromProperties (fileProperties) { + + let tagPlace = '' + log.info('fileProperties.tags', fileProperties.tags) + if (fileProperties.tags.length && rangement_instance.keepTags) { + tagPlace = ' ' + rangement_instance.tagSectionSeparator + ' ' + fileProperties.tags.join(rangement_instance.tagSeparator) + } + log.debug('fileProperties.dateStampExif', fileProperties.dateStampExif) + let newName = '' + + fileProperties.dateStampExif + + ' ' + + (rangement_instance.keepFreeText ? fileProperties.freeText : '') + + tagPlace + + fileProperties.extension + + if (rangement_instance.replaceUnderscoreWithSpaces) { + newName = newName.replace('_', ' ') + } + + newName = finder.cleanSpaces(newName) + + return newName +} + +let otherRenames = [] + +function shouldWeChangeName (structureForFile) { + log.info(' ______ shouldWeChangeName ', structureForFile.fileNameOriginal) + let newName = makeFileNameFromProperties(structureForFile) + log.debug('newName', newName) + if (structureForFile.fileNameOriginal !== newName && !otherRenames.includes(newName)) { + + log.info('\n ancien nom :', structureForFile.fileNameOriginal) + + log.info(' nouveau nom:', newName) + if (!mini_arguments['dry-run']) { + + renameFile(structureForFile.fullPath, structureForFile.folderPath + newName) + } else { + log.info('no renaming for real, this is a dry run') + finder.statistics['filesNotModified']++ + } + } else { + log.info(' rien à changer') + } +} + +/** + * guess file name on one file which is not a directory + * @param fullPath + */ +function guessFileNameOnOnefile (fullPath) { + + log.info('go guess file name on file: ', fullPath) + fs.stat(fullPath, (err, stats) => { + + if (err) { + log.error('échec fichier', err) + log.error('ce fichier n existe pas: ', fullPath) + return + } else { + + let structureForFile = finder.destructurateFileName(fullPath) + + // examiner les infos exif de chaque fichier pour proposer un nouveau nom + if (!structureForFile.dateStampInFileNameOriginal) { + log.info(' le nom de fichier "' + structureForFile.freeText + '" ne contient pas de date formatée au début') + + finder.findExifCreationDate(structureForFile.fullPath) + .then(data => { + log.info(' ... chercher la date de création : "' + structureForFile.freeText + '"') + let foundDate = finder.findEarliestDateInExifData(data) + + log.info(' =>>>>>>> foundDate : ', foundDate) + if (foundDate) { + structureForFile.dateStampExif = foundDate + } else { + log.info('pas de date trouvée dans le nom') + } + shouldWeChangeName(structureForFile) + } + , + (error) => { + log.warn('/////////// Error in reading exif of file: ' + error.message) + return '' + }) + } + + } + }) +} + +let expandedFileList = [] +let cwd = path.dirname(process.cwd()) + '/' + path.basename(process.cwd()) +log.debug('cwd', cwd) + +function guessFileNameOnAllFilesFromArguments () { + + // parcourir les fichiers + log.debug('liste des fichiers', mini_arguments._) + let fileList = mini_arguments._ + + // test file exists + fileList.forEach(fullPath => { + log.debug('file list element: ', fullPath) + // parcourir les dossiers + isFolderOrFile(`${fullPath}`) + } + ) + + log.info('expanded file list :', expandedFileList) + expandedFileList.forEach(filePath => guessFileNameOnOnefile(filePath)) + + if (rangement_instance.reportStatistics || mini_arguments.stats) { + finder.reportStatistics() + } + +} + +function readSubdirectories (baseDir) { + const newGlob = baseDir + fs.readdir(baseDir, (err, files) => { + if (err) throw err + + log.debug('readSubdirectories - files', files) + files.forEach((subDirOrFile) => { + const newFullPath = cwd + '/' + subDirOrFile + + if (fs.existsSync(newFullPath)) { + const s = fs.statSync(newFullPath) + + if (s.isFile()) { + expandedFileList.push(cwd + '/' + subDirOrFile) + } + } + }) + }) +} + +function isFolderOrFile (fileName) { + const stat = fs.statSync(fileName) + + if (stat.isDirectory()) { + let fileList = readSubdirectories(fileName) + log.debug('fileList in directory ', fileName, '\n', fileList) + if (fileList) { + expandedFileList.push(...fileList) + } + } else if (stat.isFile()) { + expandedFileList.push(fileName) + } +} + +parseArguments() + +guessFileNameOnAllFilesFromArguments() diff --git a/package-lock.json b/package-lock.json index f17b9f4..f1c2cfc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,11 +21,14 @@ "@babel/preset-env": "^7.22.5", "@babel/preset-typescript": "^7.22.5", "@jest/globals": "^29.5.0", + "@types/node": "^20.4.2", "babel-jest": "^29.5.0", "jest": "^29.5.0", "loglevel": "^1.8.1", "nodemon": "^2.0.22", - "ts-node": "^10.9.1" + "ts-node": "^10.9.1", + "tslib": "^2.6.0", + "typescript": "^5.1.6" } }, "node_modules/@ampproject/remapping": { @@ -2365,9 +2368,9 @@ } }, "node_modules/@types/node": { - "version": "20.3.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz", - "integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==", + "version": "20.4.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz", + "integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==", "dev": true }, "node_modules/@types/prettier": { @@ -5150,6 +5153,12 @@ } } }, + "node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "dev": true + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -5176,7 +5185,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 7402cd0..611c1db 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,12 @@ "name": "rangement", "version": "1.0.0", "description": "sorting utilities and file tagging using PIM methodology of Karl Voit", - "main": "index.mjs", - "type": "module", + "main": "index.ts", "scripts": { - "start": "node index.mjs", - "devine": "node index.mjs", - "test": "jest --coverage --watch" + "start": "ts-node index.ts", + "devine": "ts-node index.ts", + "test": "jest --coverage --watch", + "tests": "jest --coverage --watch" }, "repository": { "type": "git", @@ -37,10 +37,13 @@ "@babel/preset-env": "^7.22.5", "@babel/preset-typescript": "^7.22.5", "@jest/globals": "^29.5.0", + "@types/node": "^20.4.2", "babel-jest": "^29.5.0", "jest": "^29.5.0", "loglevel": "^1.8.1", "nodemon": "^2.0.22", - "ts-node": "^10.9.1" + "ts-node": "^10.9.1", + "tslib": "^2.6.0", + "typescript": "^5.1.6" } } diff --git a/tests/finder.test.js b/tests/finder.test.js index f6eca2f..eb3814f 100644 --- a/tests/finder.test.js +++ b/tests/finder.test.js @@ -1,5 +1,5 @@ -import finder from '../utils/finder.mjs' -import { makeFileNameFromProperties } from '../index' +import finder from '../utils/finder.js' +import { makeFileNameFromProperties } from '../old_index' import { expect } from '@jest/globals' describe('detection in file name', () => { diff --git a/tests/main.test.js b/tests/main.test.js index b039c47..03e5919 100644 --- a/tests/main.test.js +++ b/tests/main.test.js @@ -1,4 +1,4 @@ -import finder from "../utils/finder.mjs"; +import finder from "../utils/finder.js"; import { Jest as mockUpload } from '@jest/environment' xdescribe('rangement file detection', () => { diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9b702eb --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + // This is an alias to @tsconfig/node16: https://github.com/tsconfig/bases + "extends": "ts-node/node16/tsconfig.json", + + // Most ts-node options can be specified here using their programmatic names. + "ts-node": { + // It is faster to skip typechecking. + // Remove if you want ts-node to do typechecking. + "transpileOnly": true, + + "files": true, + + "compilerOptions": { + // compilerOptions specified here will override those declared below, + // but *only* in ts-node. Useful if you want ts-node and tsc to use + // different options with a single tsconfig.json. + } + }, + "compilerOptions": { + // typescript options here + } +} \ No newline at end of file diff --git a/utils/finder.mjs b/utils/finder.mjs deleted file mode 100644 index 55224f1..0000000 --- a/utils/finder.mjs +++ /dev/null @@ -1,311 +0,0 @@ -/** - * la classe qui repère des patterns - */ -import exifr from 'exifr' -import moment from 'moment' -import log from 'loglevel' -import rangement_instance from '../conf/configs.js' - -log.setLevel(rangement_instance.log_level) - -/** - * finds patterns for file name - */ -export default class finder { - - static statistics = { - filesModified: 0, - filesNotModified: 0, - } - - static reportStatistics () { - log.info('\n --------- statistics', - this.statistics) - } - - static findScreenshot (inputString) { - return inputString.match(/screenshot/i) || inputString.match(/capture d'écran/i) - } - - static findFormattedDate (filepath) { - let match = filepath.match(/\d{4}-\d{2}-\d{2}T\d{2}\.\d{2}\.\d{2}/ig) - log.debug(' finder - match findFormattedDate', match) - let result = '' - if (match && match[0]) { - result = match[0] - } - return result - } - - static findFileExtension (inputString) { - let result = inputString.match(/\.\w{3,4}$/i) - if (result.length) { - return result[0] - } - return '' - } - - /** - * find the section of file name which contains the free text to describe the picture - * @param fileName - * @returns {*|string} - */ - static findFileNameFreeTextPart (fileName) { - fileName = fileName.replace(this.findFileExtension(fileName), '') - let boom = fileName.split(rangement_instance.tagSectionSeparator) - if (boom.length) { - let freeTextPart = boom[0].trim() - // remove date - let foundDate = this.findFormattedDate(freeTextPart) - if (foundDate) { - freeTextPart = freeTextPart.replace(foundDate, '').trim() - } - log.debug(' finder - freeTextPart:', freeTextPart) - return freeTextPart - } - return fileName.trim() - } - - /** - * find an array of tags - * @param inputString - * @returns {[]} - */ - static findTagSectionInString (inputString) { - let listOfTags = [] - // remove extension - let extensionFile = finder.findFileExtension(inputString) - - if (extensionFile) { - - inputString = inputString.replace(extensionFile, '') - } else { - log.debug(' finder - no extensionFile', extensionFile, inputString) - extensionFile = '' - } - inputString = inputString.replace(extensionFile, '') - - log.debug(' finder - extensionFile', extensionFile) - if (inputString.includes(rangement_instance.tagSectionSeparator)) { - log.debug(' finder - inputString', inputString) - if (inputString.length) { - - let boom = inputString.split(rangement_instance.tagSectionSeparator) - log.debug(' finder - boom', boom) - if (boom.length) { - let fileSectionsName = boom.splice(rangement_instance.tagSeparator) - listOfTags = [...fileSectionsName[1].trim().split(rangement_instance.tagSeparator)] - log.debug(' finder - listOfTags', listOfTags) - } else { - log.debug(' finder - no boom', boom) - } - } - } - return listOfTags - } - - static removeTagInProperties (properties, tagName) { - - let foundTagNameIndex = properties.tags.indexOf(tagName) - if (foundTagNameIndex) { - delete properties.tags[foundTagNameIndex] - } - return properties - } - - static cleanSpaces (inputString) { - return inputString.trim().replace(/ *g/, ' ').replace(/ /, ' ') - } - - static searchAndReplaceInFileName (searchString, replaceString, fileName) { - return this.cleanSpaces(fileName.replace(searchString, replaceString)) - } - - /** - * search screenshot clues and rename - */ - static searchAndRenameScreenshots (fileName) { - if (finder.findScreenshot(fileName)) { - let tags = this.findTagSectionInString(fileName) - log.debug(' finder - tags', tags) - if (!tags.includes('screenshot')) { - - fileName = this.addTagInFileName('screenshot', fileName) - fileName = this.searchAndReplaceInFileName('Screenshot', '', fileName) - log.debug(' finder - screenShotMockFileName:', fileName) - return this.cleanSpaces(fileName) - } - log.debug(' finder - is a screenshot, remove screenshot in name, and add tag screenshot') - } else { - return null - } - } - - /** - * determines if we must add or remove new tags - * @param tagCommand - * @returns {{tagsToAdd: [], tagCommand: *, tagsToRemove: []}} - */ - static addOrRemoveTagsParsing (tagCommand) { - let tagsToAdd = [] - let tagsToRemove = [] - - // split all tags - let listOfTags = tagCommand.split(' ') - listOfTags.forEach(elem => { - // remove when a minus is present* - if (elem.indexOf('-')) { - tagsToRemove.push(elem) - } else { - // add tag otherwise - tagsToAdd.push(elem) - } - }) - - return { tagCommand, tagsToAdd, tagsToRemove } - } - - static applyTagChangesOnProperties(tagChange, properties){ - - properties.tags = [...properties.tags, tagChange.tagsToAdd] - properties.tags.filter(elem=>{ - return tagChange.tagsToRemove.includes(elem) - }) - return properties - } - - /** - * add a tag and gives new filename with extension - * @param tagName - * @param fileName - * @returns {*} - */ - static addTagInFileName (tagName, fileName) { - - let tags = this.findTagSectionInString(fileName) - let firstPart = this.findFileNameFreeTextPart(fileName) - - tags.push(tagName) - let uniqueArray = [...new Set(tags)] - - let newFileName = firstPart + ' ' + rangement_instance.tagSectionSeparator + ' ' + uniqueArray.join(rangement_instance.tagSeparator) - newFileName = newFileName.replace(/ {*}/, '') + this.findFileExtension(fileName) - return this.cleanSpaces(newFileName) - } - - /** - * convertit un nom de fichier en une structure décrivant plusieurs parties correspondant au pattern d'archivage - * @param fullPath - * @returns {{extension: *, dateStamp: string, freeText: (*|string), tags: *[]}} - */ - static destructurateFileName (fullPath) { - let [folderPath, fileNameOriginal] = this.findFolderPath(fullPath) - let dateStampInFileNameOriginal = this.findFormattedDate(fileNameOriginal) - return { - fullPath, - folderPath, - fileNameOriginal, - dateStampInFileNameOriginal, - dateStampExif: '', - freeText: this.findFileNameFreeTextPart(fileNameOriginal), - tags: this.findTagSectionInString(fileNameOriginal), - extension: this.findFileExtension(fileNameOriginal), - } - - } - - /** - * finds the earliest part in several exif date info - * @param exifData - * @returns {string} - */ - static findEarliestDateInExifData (exifData) { - log.debug(' finder - findEarliestDateInExifData') - if (exifData) { - - let moments = [] - - log.debug(' finder - exif data : ', exifData) // Do something with your data! - if (exifData.DateTimeOriginal) { - log.debug(' finder - image créée le : DateTimeOriginal : ', exifData.DateTimeOriginal) // Do something with your data! - moments.push(exifData.DateTimeOriginal) - } - if (exifData.ModificationDateTime) { - log.debug(' finder - image créée le : ModificationDateTime : ', exifData.ModificationDateTime) // Do something with your data! - moments.push(exifData.ModificationDateTime) - } - if (exifData.ModifyDate) { - log.debug(' finder - image créée le : ModifyDate : ', exifData.ModifyDate) // Do something with your data! - moments.push(exifData.ModifyDate) - } - if (exifData.FileAccessDateTime) { - moments.push(exifData.FileAccessDateTime) - } - if (exifData.FileInodeChangeDateTime) { - moments.push(exifData.FileInodeChangeDateTime) - } - if (exifData.FileModificationDateTime) { - log.debug(' finder - image créée le : FileModificationDateTime : ', exifData.FileModificationDateTime) // Do something with your data! - moments.push(exifData.FileModificationDateTime) - } - if (exifData.CreateDate) { - log.debug(' finder - image créée le : CreateDate : ', exifData.CreateDate) // Do something with your data! - moments.push(exifData.CreateDate) - } - - moments = moments.map(d => { - let newdate = moment(d) - return newdate - }) - let minDate = moment.min(moments) - - log.debug(' finder - minDate :::::::::', minDate) - log.info(' finder - minDate :::::::::', minDate.format(rangement_instance.iso_date_format)) - - return minDate.format(rangement_instance.iso_date_format) - } else { - log.debug(' finder - /!\\ pas de exif data') - return '' - } - } - - - - static appendFileName (fileProperties, newText) { - fileProperties.freeText = finder.cleanSpaces(fileProperties.freeText + ' ' + newText) - return fileProperties - } - - static prependFileName (fileProperties, newText) { - fileProperties.freeText = finder.cleanSpaces(newText + ' ' + fileProperties.freeText) - return fileProperties - } - - /** - * examine plusieurs propriétés exif de date et retourne la plus ancienne - * @param filepath - */ - static async findExifCreationDate (filepath) { - - log.debug(' finder - filepath', filepath) - let dateAlreadyInFileName = finder.findFormattedDate(filepath) - if (dateAlreadyInFileName) { - - log.debug(' finder - ------ dateAlreadyInFileName', dateAlreadyInFileName) - } - - return await exifr.parse(filepath) - - } - - static findFolderPath (filePath) { - let folders = filePath.split('/') - let fileName = folders.pop() - folders = filePath.replace(fileName, '') - - log.debug(' finder - \n - folders', folders) - log.debug(' finder - - fileName', fileName, '\n') - return [folders, fileName] - - } -} \ No newline at end of file diff --git a/utils/finder.ts b/utils/finder.ts new file mode 100644 index 0000000..42f5eb3 --- /dev/null +++ b/utils/finder.ts @@ -0,0 +1,322 @@ +/** + * la classe qui repère des patterns + */ +import exifr from 'exifr' +import moment from 'moment' +import log from 'loglevel' +import rangement_instance from '../conf/configs' + +log.setLevel(rangement_instance.log_level) + +interface fileDestructuration { + fullPath: string + folderPath: string + fileNameOriginal: string + dateStampInFileNameOriginal: string + dateStampExif: string, + freeText: string, + tags: string[], + extension: string, +} + +/** + * finds patterns for file name + */ +export default class finder { + + static statistics = { + filesModified: 0, + filesNotModified: 0, + } + + static reportStatistics() { + log.info('\n --------- statistics', + this.statistics) + } + + static findScreenshot(inputString: string) { + return inputString.match(/screenshot/i) || inputString.match(/capture d'écran/i) + } + + static findFormattedDate(filepath: string) { + let match = filepath.match(/\d{4}-\d{2}-\d{2}T\d{2}\.\d{2}\.\d{2}/ig) + log.debug(' finder - match findFormattedDate', match) + let result = '' + if (match && match[0]) { + result = match[0] + } + return result + } + + static findFileExtension(inputString: string): string { + let result = inputString.match(/\.\w{3,4}$/i) + if (result && result.length) { + return result[0] + } + return '' + } + + /** + * find the section of file name which contains the free text to describe the picture + * @param fileName + * @returns {*|string} + */ + static findFileNameFreeTextPart(fileName: string) { + fileName = fileName.replace(this.findFileExtension(fileName), '') + let boom = fileName.split(rangement_instance.tagSectionSeparator) + if (boom.length) { + let freeTextPart = boom[0].trim() + // remove date + let foundDate = this.findFormattedDate(freeTextPart) + if (foundDate) { + freeTextPart = freeTextPart.replace(foundDate, '').trim() + } + log.debug(' finder - freeTextPart:', freeTextPart) + return freeTextPart + } + return fileName.trim() + } + + /** + * find an array of tags + * @param inputString + * @returns {[]} + */ + static findTagSectionInString(inputString: string) { + let listOfTags: Array = [] + // remove extension + let extensionFile = finder.findFileExtension(inputString) + + if (extensionFile) { + + inputString = inputString.replace(extensionFile, '') + } else { + log.debug(' finder - no extensionFile', extensionFile, inputString) + extensionFile = '' + } + inputString = inputString.replace(extensionFile, '') + + log.debug(' finder - extensionFile', extensionFile) + if (inputString.includes(rangement_instance.tagSectionSeparator)) { + log.debug(' finder - inputString', inputString) + if (inputString.length) { + + let boom = inputString.split(rangement_instance.tagSectionSeparator) + log.debug(' finder - boom', boom) + if (boom.length) { + listOfTags = [...boom[1].trim().split(rangement_instance.tagSeparator)] + log.debug(' finder - listOfTags', listOfTags) + } else { + log.debug(' finder - no boom', boom) + } + } + } + return listOfTags + } + + static removeTagInProperties(properties: fileDestructuration, tagName: string) { + + let foundTagNameIndex = properties.tags.indexOf(tagName) + if (foundTagNameIndex) { + delete properties.tags[foundTagNameIndex] + } + return properties + } + + static cleanSpaces(inputString: string) { + return inputString.trim().replace(/ *g/, ' ').replace(/ /, ' ') + } + + static searchAndReplaceInFileName(searchString: string, replaceString: string, fileName: string): string { + return this.cleanSpaces(fileName.replace(searchString, replaceString)) + } + + /** + * search screenshot clues and rename + */ + static searchAndRenameScreenshots(fileName: string) { + if (finder.findScreenshot(fileName)) { + let tags = this.findTagSectionInString(fileName) + log.debug(' finder - tags', tags) + if (!tags.includes('screenshot')) { + + fileName = this.addTagInFileName('screenshot', fileName) + fileName = this.searchAndReplaceInFileName('Screenshot', '', fileName) + log.debug(' finder - screenShotMockFileName:', fileName) + return this.cleanSpaces(fileName) + } + log.debug(' finder - is a screenshot, remove screenshot in name, and add tag screenshot') + } else { + return null + } + } + + /** + * determines if we must add or remove new tags + * @param tagCommand + * @returns {{tagsToAdd: [], tagCommand: *, tagsToRemove: []}} + */ + static addOrRemoveTagsParsing(tagCommand: string): any { + let tagsToAdd: Array = [] + let tagsToRemove: Array = [] + + // split all tags + let listOfTags = tagCommand.split(' ') + listOfTags.forEach(elem => { + // remove when a minus is present* + if (elem.indexOf('-')) { + tagsToRemove.push(elem) + } else { + // add tag otherwise + tagsToAdd.push(elem) + } + }) + + return {tagCommand, tagsToAdd, tagsToRemove} + } + + static applyTagChangesOnProperties(tagChange: any, properties: fileDestructuration) { + + + properties.tags = [...properties.tags, tagChange.tagsToAdd] + properties.tags.filter(elem => { + return tagChange.tagsToRemove.includes(elem) + }) + return properties + } + + /** + * add a tag and gives new filename with extension + * @param tagName + * @param fileName + * @returns {*} + */ + static addTagInFileName(tagName:string, fileName:string) { + + let tags:any = this.findTagSectionInString(fileName) + let firstPart = this.findFileNameFreeTextPart(fileName) + + tags.push(tagName) + // @ts-ignore + let uniqueArray = [...new Set(tags)] + + let newFileName = firstPart + ' ' + rangement_instance.tagSectionSeparator + ' ' + uniqueArray.join(rangement_instance.tagSeparator) + newFileName = newFileName.replace(/ {*}/, '') + this.findFileExtension(fileName) + return this.cleanSpaces(newFileName) + } + + /** + * convertit un nom de fichier en une structure décrivant plusieurs parties correspondant au pattern d'archivage + * @param fullPath + * @returns {{extension: *, dateStamp: string, freeText: (*|string), tags: *[]}} + */ + static destructurateFileName(fullPath:string) { + let [folderPath, fileNameOriginal] = this.findFolderPath(fullPath) + let dateStampInFileNameOriginal = this.findFormattedDate(fileNameOriginal) + return { + fullPath, + folderPath, + fileNameOriginal, + dateStampInFileNameOriginal, + dateStampExif: '', + freeText: this.findFileNameFreeTextPart(fileNameOriginal), + tags: this.findTagSectionInString(fileNameOriginal), + extension: this.findFileExtension(fileNameOriginal), + } + + } + + /** + * finds the earliest part in several exif date info + * @param exifData + * @returns {string} + */ + static findEarliestDateInExifData(exifData:any) { + log.debug(' finder - findEarliestDateInExifData') + if (exifData) { + + let moments = [] + + log.debug(' finder - exif data : ', exifData) // Do something with your data! + if (exifData.DateTimeOriginal) { + log.debug(' finder - image créée le : DateTimeOriginal : ', exifData.DateTimeOriginal) // Do something with your data! + moments.push(exifData.DateTimeOriginal) + } + if (exifData.ModificationDateTime) { + log.debug(' finder - image créée le : ModificationDateTime : ', exifData.ModificationDateTime) // Do something with your data! + moments.push(exifData.ModificationDateTime) + } + if (exifData.ModifyDate) { + log.debug(' finder - image créée le : ModifyDate : ', exifData.ModifyDate) // Do something with your data! + moments.push(exifData.ModifyDate) + } + if (exifData.FileAccessDateTime) { + moments.push(exifData.FileAccessDateTime) + } + if (exifData.FileInodeChangeDateTime) { + moments.push(exifData.FileInodeChangeDateTime) + } + if (exifData.FileModificationDateTime) { + log.debug(' finder - image créée le : FileModificationDateTime : ', exifData.FileModificationDateTime) // Do something with your data! + moments.push(exifData.FileModificationDateTime) + } + if (exifData.CreateDate) { + log.debug(' finder - image créée le : CreateDate : ', exifData.CreateDate) // Do something with your data! + moments.push(exifData.CreateDate) + } + + moments = moments.map(d => { + let newdate = moment(d) + return newdate + }) + let minDate = moment.min(moments) + + log.debug(' finder - minDate :::::::::', minDate) + log.info(' finder - minDate :::::::::', minDate.format(rangement_instance.iso_date_format)) + + return minDate.format(rangement_instance.iso_date_format) + } else { + log.debug(' finder - /!\\ pas de exif data') + return '' + } + } + + + static appendFileName(fileProperties:fileDestructuration, newText:string) { + fileProperties.freeText = finder.cleanSpaces(fileProperties.freeText + ' ' + newText) + return fileProperties + } + + static prependFileName(fileProperties:fileDestructuration, newText:string) { + fileProperties.freeText = finder.cleanSpaces(newText + ' ' + fileProperties.freeText) + return fileProperties + } + + /** + * examine plusieurs propriétés exif de date et retourne la plus ancienne + * @param filepath + */ + static async findExifCreationDate(filepath:string) { + + log.debug(' finder - filepath', filepath) + let dateAlreadyInFileName = finder.findFormattedDate(filepath) + if (dateAlreadyInFileName) { + + log.debug(' finder - ------ dateAlreadyInFileName', dateAlreadyInFileName) + } + + return await exifr.parse(filepath) + + } + + static findFolderPath(filePath:string) { + let folders:any = filePath.split('/') + let fileName:any = folders.pop() + folders = filePath.replace(fileName, '') + + log.debug(' finder - \n - folders', folders) + log.debug(' finder - - fileName', fileName, '\n') + return [folders, fileName] + + } +} \ No newline at end of file diff --git a/utils/testFunctions.mjs b/utils/testFunctions.mjs index 548b4dd..e99e815 100644 --- a/utils/testFunctions.mjs +++ b/utils/testFunctions.mjs @@ -1,4 +1,4 @@ -import finder from './finder.mjs' +import finder from './finder.js' const pathFolder = '/home/poule/encrypted/stockage-syncable/photos/a_dispatcher/tout' diff --git a/utils/workInProgress/not_ready_functions.mjs b/utils/workInProgress/not_ready_functions.mjs index 229bb60..9b17641 100644 --- a/utils/workInProgress/not_ready_functions.mjs +++ b/utils/workInProgress/not_ready_functions.mjs @@ -1,5 +1,7 @@ import finder from './finders' import fs from 'node-fs' +import rangement_instance from '../../conf/configs' +import { TestFindFormattedDate, TestScreenShotIsFoundAndRenamed, TestTagsAreDetectedInFileName } from '../testFunctions' /** ----------------------- parties non réalisées ----------------------- @@ -132,4 +134,16 @@ class notReadyFunctions { } + +// run tests +if (rangement_instance.enableTestsLocally) { + + TestTagsAreDetectedInFileName() + TestFindFormattedDate() + TestScreenShotIsFoundAndRenamed() +} + + + + export default notReadyFunctions \ No newline at end of file