import { TagNode } from "./tag-node" ; import { FileNode } from "./file-node" ; class TagTree { public root : TagNode ; private fileIndex : Map < string , TagNode [ ] > ; constructor ( ) { this . root = new TagNode ( null , "" , "" ) ; this . fileIndex = new Map ( ) ; } public addFile ( filePath , tags , displayName ) { for ( const tag of tags ) { const newNode = new FileNode ( this . createKeyForFile ( filePath ) , filePath , tag , tags , displayName ) ; this . addFileNode ( tag , newNode ) ; } } public deleteFile ( filePath ) { const fileKey = this . createKeyForFile ( filePath ) ; if ( ! this . fileIndex . has ( fileKey ) ) { return ; } const tags = this . fileIndex . get ( fileKey ) ; for ( const tag of tags ! ) { tag . deleteFile ( fileKey ) ; this . deletePathToFile ( tag ) ; } this . fileIndex . delete ( fileKey ) ; } public getTagsForFile ( filePath ) { const fileKey = this . createKeyForFile ( filePath ) ; if ( ! this . fileIndex . has ( fileKey ) ) { return new Set ( ) ; } const tagNodes = this . fileIndex . get ( fileKey ) ! ; return tagNodes . reduce ( ( tags , tagNode ) => { return tags . add ( tagNode . tag ) ; } , new Set ( ) ) ; } public getNode ( nodePath ) { return nodePath . split ( "/" ) . reduce ( ( currentNode , pathPart ) => { if ( pathPart . includes ( "." ) ) { return currentNode . files . get ( this . createKeyForFile ( pathPart ) ) ; } else { return currentNode . tags . get ( pathPart ) ; } } , this . root ) ; } private addFileNode ( tagPath , fileNode ) { /**
         * Given a tag filePath, the tags that lead up to the file may not exist in the tag tree continue down the filePath
         * building the parts of the tree that don't exist one node at a time
         */ const nodeToAddFileTo = tagPath . split ( "/" ) . reduce ( ( { currentNode , currentPath } , pathToAdd ) => { const newCurrentPath = currentPath === "" ? pathToAdd : `${ currentPath }/${ pathToAdd }` ; if ( currentNode . getTag ( pathToAdd ) !== undefined ) { return { currentNode : currentNode . getTag ( pathToAdd ) ! , currentPath : newCurrentPath } ; } else { currentNode . addTag ( pathToAdd , newCurrentPath ) ; return { currentNode : currentNode . getTag ( pathToAdd ) ! , currentPath : newCurrentPath } ; } } , { currentNode : this . root , currentPath : "" } ) . currentNode ; /**
         * At this point, the tag tree has been built and we can just add the file to the
         * tag tree
         */ nodeToAddFileTo . addFile ( fileNode ) ; if ( ! this . fileIndex . has ( fileNode . key ) ) { this . fileIndex . set ( fileNode . key , [ nodeToAddFileTo ] ) ; } else { const nodesForFile = this . fileIndex . get ( fileNode . key ) ! ; this . fileIndex . set ( fileNode . key , [ ... nodesForFile , nodeToAddFileTo ] ) ; } } private createKeyForFile ( filePath ) { return filePath . replace ( /\//g , "|" ) ; } private deletePathToFile ( node ) { let currentNode = node ; while ( currentNode ! . files . size === 0 && currentNode ! . tags . size === 0 ) { const { parent } = currentNode ; if ( parent !== null ) { parent ! . deleteTag ( currentNode . tag ) ; currentNode = parent ; } else { break ; } } } } export { TagTree } ;