-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: created util functions for sasjs fs compile command
- Loading branch information
Showing
4 changed files
with
286 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import path from 'path' | ||
import { isFolder, listFilesInFolder, listSubFoldersInFolder } from '../file' | ||
import { | ||
getInitialCode, | ||
getCompiledMacrosCode, | ||
generateCodeForFileCreation | ||
} from './internal/helper' | ||
import { HashedFolder } from '../types' | ||
|
||
export const generateCompileProgram = async (folderPath: string) => { | ||
const compiledMacrosCode = await getCompiledMacrosCode(['mf_mkdir.sas']) | ||
|
||
const initialProgramContent = getInitialCode() | ||
|
||
const folderCreationCode = await fileAndDirectoryCreationCode(folderPath) | ||
|
||
return compiledMacrosCode + initialProgramContent + folderCreationCode | ||
} | ||
|
||
const fileAndDirectoryCreationCode = async ( | ||
resourcePath: string, | ||
pathRelativeTo: string = resourcePath, | ||
resultCode: string = '' | ||
) => { | ||
if (!(await isFolder(resourcePath))) { | ||
resultCode += await generateCodeForFileCreation( | ||
resourcePath, | ||
pathRelativeTo | ||
) | ||
return resultCode | ||
} | ||
|
||
const files = await listFilesInFolder(resourcePath) | ||
for (const file of files) { | ||
resultCode = await fileAndDirectoryCreationCode( | ||
path.join(resourcePath, file), | ||
pathRelativeTo, | ||
resultCode | ||
) | ||
} | ||
|
||
const subFolders = await listSubFoldersInFolder(resourcePath) | ||
for (const folder of subFolders) { | ||
const folderPath = path.join(resourcePath, folder) | ||
|
||
resultCode = `${resultCode} | ||
%mf_mkdir(&fsTarget${folderPath.replace(pathRelativeTo, '')}) | ||
` | ||
|
||
resultCode = await fileAndDirectoryCreationCode( | ||
folderPath, | ||
pathRelativeTo, | ||
resultCode | ||
) | ||
} | ||
|
||
return resultCode | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import path from 'path' | ||
import { getNodeModulePath } from '../../utils/getNodeModulePath' | ||
import { chunk } from '../../utils/chunk' | ||
import { readFile, base64EncodeFile } from '../../file' | ||
|
||
export const generateCodeForFileCreation = async ( | ||
filePath: string, | ||
pathRelativeTo: string | ||
) => { | ||
const base64EncodedFileContent = await base64EncodeFile(filePath) | ||
const chunkedFileContent = chunkFileContent(base64EncodedFileContent) | ||
return ` | ||
filename _in64 temp lrecl=99999999; | ||
data _null_; | ||
file _in64; | ||
${chunkedFileContent} | ||
run; | ||
filename _out64 "&fsTarget${filePath.replace(pathRelativeTo, '')}"; | ||
/* convert from base64 */ | ||
data _null_; | ||
length filein 8 fileout 8; | ||
filein = fopen("_in64",'I',4,'B'); | ||
fileout = fopen("_out64",'O',3,'B'); | ||
char= '20'x; | ||
do while(fread(filein)=0); | ||
length raw $4 ; | ||
do i=1 to 4; | ||
rc=fget(filein,char,1); | ||
substr(raw,i,1)=char; | ||
end; | ||
rc = fput(fileout, input(raw,$base64X4.)); | ||
rc =fwrite(fileout); | ||
end; | ||
rc = fclose(filein); | ||
rc = fclose(fileout); | ||
run; | ||
filename _in64 clear; | ||
filename _out64 clear; | ||
` | ||
} | ||
|
||
export const chunkFileContent = (fileContent: string) => { | ||
const chunkedLines = chunk(fileContent) | ||
|
||
if (chunkedLines.length === 1) { | ||
return ` put '${chunkedLines[0].split("'").join("''")}';\n` | ||
} | ||
|
||
let combinedLines = '' | ||
|
||
chunkedLines.forEach((chunkedLine, index) => { | ||
const text = ` put '${chunkedLine.split("'").join("''")}'${ | ||
index !== chunkedLines.length - 1 ? '@;\n' : ';\n' | ||
}` | ||
|
||
combinedLines += text | ||
}) | ||
|
||
return combinedLines | ||
} | ||
|
||
export const getInitialCode = () => `%global fsTarget; | ||
%let compiled_fsTarget=%sysfunc(pathname(work)); | ||
%let fsTarget=%sysfunc(coalescec(&fsTarget,&compiled_fsTarget)); | ||
options nobomfile; | ||
%mf_mkdir(&fsTarget) | ||
` | ||
|
||
export const getCompiledMacrosCode = async (macros: string[]) => { | ||
const sasjsCorePath = await getNodeModulePath('@sasjs/core') | ||
if (!sasjsCorePath) throw new Error('@sasjs/core could not be found') | ||
|
||
let compiledCode = '' | ||
|
||
for (const macro of macros) { | ||
const macroPath = path.join(sasjsCorePath, 'base', macro) | ||
const macroContent = await readFile(macroPath) | ||
|
||
compiledCode += macroContent + '\n' | ||
} | ||
|
||
return compiledCode | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import path from 'path' | ||
import { generateCompileProgram } from '../generateCompileProgram' | ||
import { createFile, createFolder, deleteFolder, readFile } from '../../file' | ||
|
||
describe('createFSCompileProgram', () => { | ||
const timestamp = new Date().valueOf() | ||
const folderName = `test-create-folder-${timestamp}` | ||
const folderPath = path.join(__dirname, folderName) | ||
const subFolderPath = path.join(folderPath, 'subFolder') | ||
const filePath = path.join(subFolderPath, 'file.txt') | ||
|
||
beforeAll(async () => { | ||
await createFolder(folderPath) | ||
await createFolder(subFolderPath) | ||
await createFile(filePath, 'this is dummy file content') | ||
}) | ||
|
||
afterAll(async () => { | ||
await deleteFolder(folderPath) | ||
}) | ||
|
||
it('should return a sas program ', async () => { | ||
const program = await generateCompileProgram(folderPath) | ||
const expectedProgram = await readFile( | ||
path.join(__dirname, 'files', 'compiledProgram.sas') | ||
) | ||
expect(program).toEqual(expectedProgram) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
%global fsTarget; | ||
%let compiled_fsTarget=%sysfunc(pathname(work)); | ||
%let fsTarget=%sysfunc(coalescec(&fsTarget,&compiled_fsTarget)); | ||
options nobomfile; | ||
|
||
/* mf_mkdir macro begins */ | ||
/** | ||
@file | ||
@brief Creates a directory, including any intermediate directories | ||
@details Works on windows and unix environments via dcreate function. | ||
Usage: | ||
%mf_mkdir(/some/path/name) | ||
@param dir relative or absolute pathname. Unquoted. | ||
@version 9.2 | ||
**/ | ||
|
||
%macro mf_mkdir(dir | ||
)/*/STORE SOURCE*/; | ||
|
||
%local lastchar child parent; | ||
|
||
%let lastchar = %substr(&dir, %length(&dir)); | ||
%if (%bquote(&lastchar) eq %str(:)) %then %do; | ||
/* Cannot create drive mappings */ | ||
%return; | ||
%end; | ||
|
||
%if (%bquote(&lastchar)=%str(/)) or (%bquote(&lastchar)=%str(\)) %then %do; | ||
/* last char is a slash */ | ||
%if (%length(&dir) eq 1) %then %do; | ||
/* one single slash - root location is assumed to exist */ | ||
%return; | ||
%end; | ||
%else %do; | ||
/* strip last slash */ | ||
%let dir = %substr(&dir, 1, %length(&dir)-1); | ||
%end; | ||
%end; | ||
|
||
%if (%sysfunc(fileexist(%bquote(&dir))) = 0) %then %do; | ||
/* directory does not exist so prepare to create */ | ||
/* first get the childmost directory */ | ||
%let child = %scan(&dir, -1, %str(/\:)); | ||
|
||
/* | ||
If child name = path name then there are no parents to create. Else | ||
they must be recursively scanned. | ||
*/ | ||
|
||
%if (%length(&dir) gt %length(&child)) %then %do; | ||
%let parent = %substr(&dir, 1, %length(&dir)-%length(&child)); | ||
%mf_mkdir(&parent) | ||
%end; | ||
|
||
/* | ||
Now create the directory. Complain loudly of any errs. | ||
*/ | ||
|
||
%let dname = %sysfunc(dcreate(&child, &parent)); | ||
%if (%bquote(&dname) eq ) %then %do; | ||
%put %str(ERR)OR: could not create &parent + &child; | ||
%abort cancel; | ||
%end; | ||
%else %do; | ||
%put Directory created: &dir; | ||
%end; | ||
%end; | ||
/* exit quietly if directory did exist.*/ | ||
%mend mf_mkdir; | ||
|
||
/* mf_mkdir macro ends */ | ||
|
||
%mf_mkdir(&fsTarget) | ||
|
||
|
||
%mf_mkdir(&fsTarget/subFolder) | ||
|
||
filename _in64 temp lrecl=99999999; | ||
data _null_; | ||
file _in64; | ||
put 'dGhpcyBpcyBkdW1teSBmaWxlIGNvbnRlbnQ='; | ||
|
||
run; | ||
|
||
filename _out64 "&fsTarget/subFolder/file.txt"; | ||
|
||
/* convert from base64 */ | ||
data _null_; | ||
length filein 8 fileout 8; | ||
filein = fopen("_in64",'I',4,'B'); | ||
fileout = fopen("_out64",'O',3,'B'); | ||
char= '20'x; | ||
do while(fread(filein)=0); | ||
length raw $4 ; | ||
do i=1 to 4; | ||
rc=fget(filein,char,1); | ||
substr(raw,i,1)=char; | ||
end; | ||
rc = fput(fileout, input(raw,$base64X4.)); | ||
rc =fwrite(fileout); | ||
end; | ||
rc = fclose(filein); | ||
rc = fclose(fileout); | ||
run; | ||
|
||
filename _in64 clear; | ||
filename _out64 clear; | ||
|