Skip to content

Commit

Permalink
feat: created util functions for sasjs fs compile command
Browse files Browse the repository at this point in the history
  • Loading branch information
sabhas committed Oct 19, 2022
1 parent 5748a59 commit b13e82f
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 0 deletions.
58 changes: 58 additions & 0 deletions src/fs/generateCompileProgram.ts
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
}
87 changes: 87 additions & 0 deletions src/fs/internal/helper.ts
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
}
29 changes: 29 additions & 0 deletions src/fs/spec/createFSCompileProgram.spec.ts
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)
})
})
112 changes: 112 additions & 0 deletions src/fs/spec/files/compiledProgram.sas
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;

0 comments on commit b13e82f

Please sign in to comment.