import * as fs from 'fs' ; import * as path from 'path' ; import * as vscode from 'vscode' ; import { CRSALLangExtHelper } from '../crsAlLangExtHelper' ; import { AZSymbolKind } from '../symbollibraries/azSymbolKind' ; import { AZSymbolInformation } from '../symbollibraries/azSymbolInformation' ; import { StringHelper } from '../tools/stringHelper' ; export class FileBuilder { public static async showFile ( filePath ) { try { let document = await vscode . workspace . openTextDocument ( filePath ) ; vscode . window . showTextDocument ( document , { preview : false } ) ; } catch ( err ) { vscode . window . showErrorMessage ( err ) ; } } public static async showNewUntitledDocument ( content ) { try { let document = await vscode . workspace . openTextDocument ( { content : content , language : "al" } ) ; vscode . window . showTextDocument ( document , { preview : false } ) ; } catch ( err ) { vscode . window . showErrorMessage ( err ) ; } } public static async generateObjectFileInRelativeDir ( content , fileName , relativeFileDir ) { if ( ( ! vscode . workspace . workspaceFolders ) || ( vscode . workspace . workspaceFolders . length == 0 ) ) return undefined ; let workspaceFolder ; if ( vscode . workspace . workspaceFolders . length == 1 ) workspaceFolder = vscode . workspace . workspaceFolders [ 0 ] ; else { workspaceFolder = await vscode . window . showWorkspaceFolderPick ( ) ; } if ( ! workspaceFolder || workspaceFolder . uri . scheme !== 'file' ) return undefined ; const baseFileDir = workspaceFolder . uri . fsPath ; let newFileDirectory ; if ( relativeFileDir ) newFileDirectory = path . join ( baseFileDir , relativeFileDir ) ; else newFileDirectory = baseFileDir ; if ( relativeFileDir ) { try { if ( ! fs . existsSync ( newFileDirectory ) ) { this . mkdirRecursiveSync ( baseFileDir , relativeFileDir ) ; } } catch ( err ) { vscode . window . showErrorMessage ( 'Could not create the path due to the following error: ' + err ) ; return undefined ; } } const newFilePath = path . join ( newFileDirectory , fileName ) ; const fileAlreadyExists = await this . fileExists ( newFilePath ) ; if ( ! fileAlreadyExists ) { try { fs . appendFileSync ( newFilePath , content ) ; } catch ( e ) { vscode . window . showErrorMessage ( 'Cannot create the AL file, please check if you selected correct output directory and your "CRS.FileNamePattern" setting. Error: ' + e . message ) ; return undefined ; } } return newFilePath ; } public static generateObjectFileInDir ( dirPath , fileName , content ) { try { let parsedPath = path . parse ( fileName ) ; let newFilePath = path . join ( dirPath , fileName ) ; let fileIndex = 0 ; while ( fs . existsSync ( newFilePath ) ) { fileIndex ++ ; fileName = parsedPath . name + ' ' + fileIndex . toString ( ) + parsedPath . ext ; newFilePath = path . join ( dirPath , fileName ) ; } this . mkdirFullPathRecursiveSync ( dirPath ) ; fs . appendFileSync ( newFilePath , content ) ; return newFilePath ; } catch ( e ) { vscode . window . showErrorMessage ( 'Cannot create the AL file, please check if you selected correct output directory and your "CRS.FileNamePattern" setting. Error: ' + e . message ) ; return undefined ; } } private static mkdirFullPathRecursiveSync ( destPath ) { if ( fs . existsSync ( destPath ) ) return ; let parsedPath = path . parse ( destPath ) ; if ( parsedPath . dir ) this . mkdirFullPathRecursiveSync ( parsedPath . dir ) ; fs . mkdirSync ( destPath ) ; } private static mkdirRecursiveSync ( baseDir , relativeDir ) { relativeDir . split ( /[\/\\]/g ) . reduce ( ( prevDirPath , dirToCreate ) => { const curDirPathToCreate = path . resolve ( baseDir , prevDirPath , dirToCreate ) ; try { fs . mkdirSync ( curDirPathToCreate ) ; } catch ( err ) { if ( err . code !== 'EEXIST' ) { throw err ; } } return curDirPathToCreate ; } , '' ) ; } private static fileExists ( path ) { return new Promise ( ( resolve , reject ) => { fs . exists ( path , exists => { resolve ( exists ) ; } ) ; } ) ; } public static SymbolKindToCrsName ( symbolKind ) { switch ( symbolKind ) { case AZSymbolKind . TableObject : return 'table' ; case AZSymbolKind . CodeunitObject : return 'codeunit' ; case AZSymbolKind . PageObject : return 'page' ; case AZSymbolKind . ReportObject : return 'report' ; case AZSymbolKind . QueryObject : return 'query' ; case AZSymbolKind . XmlPortObject : return 'xmlport' ; case AZSymbolKind . TableExtensionObject : return 'tableextension' ; case AZSymbolKind . PageExtensionObject : return 'pageextension' ; case AZSymbolKind . ControlAddInObject : return 'controladdin' ; case AZSymbolKind . ProfileObject : return 'profile' ; case AZSymbolKind . PageCustomizationObject : return 'pagecustomization' ; case AZSymbolKind . EnumType : return 'enum' ; case AZSymbolKind . DotNetPackage : return 'dotnetpackage' ; case AZSymbolKind . EnumExtensionType : return 'enumextension' ; case AZSymbolKind . Interface : return 'interface' ; case AZSymbolKind . ReportExtensionObject : return 'reportextension' ; case AZSymbolKind . PermissionSet : return 'permissionset' ; case AZSymbolKind . PermissionSetExtension : return 'permissionsetextension' ; case AZSymbolKind . Entitlement : return 'entitlement' ; } return '' ; } public static SymbolKindToCamelCaseName ( symbolKind ) { switch ( symbolKind ) { case AZSymbolKind . TableObject : return 'Table' ; case AZSymbolKind . CodeunitObject : return 'Codeunit' ; case AZSymbolKind . PageObject : return 'Page' ; case AZSymbolKind . ReportObject : return 'Report' ; case AZSymbolKind . QueryObject : return 'Query' ; case AZSymbolKind . XmlPortObject : return 'XmlPort' ; case AZSymbolKind . TableExtensionObject : return 'TableExtension' ; case AZSymbolKind . PageExtensionObject : return 'PageExtension' ; case AZSymbolKind . ControlAddInObject : return 'ControlAddIn' ; case AZSymbolKind . ProfileObject : return 'Profile' ; case AZSymbolKind . PageCustomizationObject : return 'PageCustomization' ; case AZSymbolKind . EnumType : return 'Enum' ; case AZSymbolKind . DotNetPackage : return 'DotNetPackage' ; case AZSymbolKind . EnumExtensionType : return 'EnumExtension' ; case AZSymbolKind . Interface : return 'Interface' ; case AZSymbolKind . ReportExtensionObject : return 'ReportExtension' ; case AZSymbolKind . PermissionSet : return 'PermissionSet' ; case AZSymbolKind . PermissionSetExtension : return 'PermissionSetExtension' ; case AZSymbolKind . Entitlement : return 'Entitlement' ; } return '' ; } public static getAutoGenerateFiles ( ) { return vscode . workspace . getConfiguration ( 'alOutline' ) . get ( 'autoGenerateFiles' ) ; } public static getPromptForObjectName ( ) { return vscode . workspace . getConfiguration ( 'alOutline' ) . get ( 'promptForObjectName' ) ; } public static checkCrsFileNamePatternRequired ( ) { if ( FileBuilder . getAutoGenerateFiles ( ) && ! FileBuilder . hasCrsFileNamePattern ( ) ) { vscode . window . showErrorMessage ( 'File name pattern for new objects has not been specified in the "CRS.FileNamePattern" setting. Please add this setting to your VS Code workspace or user settings.' ) ; return false ; } return true ; } public static checkCrsExtensionFileNamePatternRequired ( ) { if ( FileBuilder . getAutoGenerateFiles ( ) && ! FileBuilder . hasCrsExtensionFileNamePattern ( ) ) { vscode . window . showErrorMessage ( 'File name pattern for extension objects has not been specified in the "CRS.FileNamePatternExtensions" setting. Please add this setting to your VS Code workspace or user settings.' ) ; return false ; } return true ; } public static checkCrsExtensionObjectNamePatternRequired ( multipleObjects ) { if ( multipleObjects && ! FileBuilder . hasCrsExtensionObjectNamePattern ( ) ) { vscode . window . showErrorMessage ( 'Extension object name pattern has not been specified in the "CRS.ExtensionObjectNamePattern" setting. Please add this setting to your VS Code workspace or user settings.' ) ; return false ; } else if ( ! multipleObjects && ! FileBuilder . hasCrsExtensionObjectNamePattern ( ) && ! FileBuilder . getPromptForObjectName ( ) ) { vscode . window . showErrorMessage ( 'The "CRS.ExtensionObjectNamePattern" setting is undefined and the "alOutline.promptForObjectName" setting is disabled. Please update your VS Code workspace or user settings.' ) ; return false ; } return true ; } public static hasCrsFileNamePattern ( ) { let patternText = vscode . workspace . getConfiguration ( 'CRS' , null ) . get ( 'FileNamePattern' ) ; if ( ! patternText ) return false ; return true ; } public static hasCrsExtensionFileNamePattern ( ) { let patternText = vscode . workspace . getConfiguration ( 'CRS' , null ) . get ( 'FileNamePatternExtensions' ) ; if ( ! patternText ) return false ; return true ; } public static hasCrsExtensionObjectNamePattern ( ) { let patternText = vscode . workspace . getConfiguration ( 'CRS' , null ) . get ( 'ExtensionObjectNamePattern' ) ; if ( ! patternText ) return false ; return true ; } public static async getPatternGeneratedFullObjectFileName ( objectType , objectId , objectName ) { let crsLangExt = await CRSALLangExtHelper . GetCrsAlLangExt ( ) ; if ( ( crsLangExt ) && ( this . hasCrsFileNamePattern ( ) ) ) return crsLangExt . ObjectNamesApi . GetObjectFileName ( this . SymbolKindToCrsName ( objectType ) , objectId . toString ( ) , objectName ) ; return objectName + '.al' ; } public static async getPatternGeneratedExtensionObjectName ( extensionType , extensionId , baseSymbolInfo ) { let crsLangExt = await CRSALLangExtHelper . GetCrsAlLangExt ( ) ; if ( ( crsLangExt ) && ( this . hasCrsExtensionObjectNamePattern ( ) ) ) return await crsLangExt . ObjectNamesApi . GetObjectExtensionName ( this . SymbolKindToCrsName ( extensionType ) , extensionId . toString ( ) , '' , this . getObjectIdFromSymbolInfo ( baseSymbolInfo ) . toString ( ) , this . getObjectNameFromSymbolInfo ( baseSymbolInfo ) ) ; return '' ; } public static async getPatternGeneratedExtensionObjectFileName ( extensionType , extensionId , extensionObjectName , baseSymbolInfo ) { let crsLangExt = await CRSALLangExtHelper . GetCrsAlLangExt ( ) ; if ( ( crsLangExt ) && ( this . hasCrsExtensionFileNamePattern ( ) ) ) return await crsLangExt . ObjectNamesApi . GetObjectExtensionFileName ( this . SymbolKindToCrsName ( extensionType ) , extensionId . toString ( ) , extensionObjectName , this . getObjectIdFromSymbolInfo ( baseSymbolInfo ) . toString ( ) , this . getObjectNameFromSymbolInfo ( baseSymbolInfo ) ) ; return extensionObjectName + '.al' ; } public static async getPatternGeneratedRelativeFilePath ( objectType ) { let typeName = this . getObjectTypeFromSymbolInfo ( objectType ) ; let typeNameCC = this . SymbolKindToCamelCaseName ( objectType ) ; let shortTypeName = '' ; let output = StringHelper . emptyIfNotDef ( vscode . workspace . getConfiguration ( 'alOutline' ) . get ( 'autoGenerateFileDirectory' ) ) ; let crsLangExt = await CRSALLangExtHelper . GetCrsAlLangExt ( ) ; if ( crsLangExt ) shortTypeName = crsLangExt . ObjectNamesApi . GetBestPracticeAbbreviatedObjectType ( this . SymbolKindToCrsName ( objectType ) ) ; output = this . replacePattern ( output , '<ObjectTypeLC>' , typeName ) ; output = this . replacePattern ( output , '<ObjectType>' , typeNameCC ) ; output = this . replacePattern ( output , '<ObjectTypeShort>' , shortTypeName ) ; return output ; } private static getObjectIdFromSymbolInfo ( alSymbolInfo ) { return alSymbolInfo . id ; } private static getObjectNameFromSymbolInfo ( alSymbolInfo ) { return this . stripNonAlphaNumericCharacters ( alSymbolInfo . name ) ; } private static getObjectTypeFromSymbolInfo ( alSymbolKind ) { return this . SymbolKindToCrsName ( alSymbolKind ) ; } private static replacePattern ( name , pattern , replaceWith ) { return name . replace ( new RegExp ( this . escapeRegExpInPattern ( pattern ) , 'g' ) , replaceWith ) ; } param { string } name returns private static escapeRegExpInPattern ( name ) { return name . replace ( /([.*+?^${}()|\[\]\/\\=!:])/g , "\\$1" ) ; } param { string } name returns public static stripNonAlphaNumericCharacters ( name ) { return name . replace ( /\W/g , "" ) ; } }