1
0
mirror of https://github.com/sasjs/core.git synced 2025-12-28 21:30:05 +00:00

Compare commits

...

20 Commits

Author SHA1 Message Date
Allan Bowe
ada9192337 Merge pull request #65 from sasjs/issue64
fix: increasing limit for mv_getfoldermember.sas, also adding a test,…
2021-08-20 22:50:27 +03:00
Allan Bowe
6161f588a9 fix: increasing limit for mv_getfoldermember.sas, also adding a test, and updating mf_getapploc to search _program by default (and fixing it to work with macro tests, and updating the test for that also) 2021-08-20 22:38:56 +03:00
Allan Bowe
67079d8c17 fix: adding default value for exists in mf_existfunction 2021-08-19 00:20:49 +03:00
Allan Bowe
75bd39adb0 Merge pull request #63 from sasjs/issue62
feat: adding FCMP capability
2021-08-19 00:00:15 +03:00
Allan Bowe
078bdbeecf chore: bumping devDependency (sasjs cli) 2021-08-18 23:47:17 +03:00
Allan Bowe
8ddb86785c feat: new fcmp stpsrv_header function 2021-08-18 23:45:45 +03:00
Allan Bowe
005af0ecf8 feat: new mf_existfunction macro 2021-08-18 23:36:12 +03:00
Allan Bowe
bc410a9135 chore: fixing docs for tests 2021-08-18 19:55:05 +03:00
Allan Bowe
fc8ba2e36c chore: moving files to tidy up docs 2021-08-18 19:43:38 +03:00
Allan Bowe
756441384a feat: adding first fcmp macro, mcf_string2file.sas 2021-08-18 18:35:51 +03:00
Allan Bowe
10f9eecf9e Merge pull request #60 from sasjs/vpn-fix
chore: vpn fix
2021-08-13 14:41:25 +03:00
470ebb50a7 chore: vpn fix 2021-08-13 12:57:31 +02:00
Allan Bowe
26cd5d9d31 Merge pull request #59 from Stefan-Dimitrov-Stoyanov/patch-1
Update README.md
2021-08-12 11:59:25 +03:00
Stefan-Dimitrov-Stoyanov
0b694bb878 Update README.md
Fix the missing space at the end of the first line under the Installation header:  https://github.com/sasjs/core/blob/main/README.md#installation
2021-08-11 13:34:44 +01:00
Allan Bowe
b403c02bba chore: docs for mm_createfolder 2021-08-06 15:36:55 +03:00
Allan Bowe
0b555bb31c Merge pull request #58 from sasjs/apploc
feat: new mf_getapploc macro
2021-08-04 22:04:55 +03:00
Allan Bowe
40b513a9e3 feat: new mf_getapploc macro 2021-08-04 22:00:18 +03:00
Allan Bowe
4eacf4deae Merge pull request #57 from sasjs/mf_existfref
fix: showing filerefs that exist (even when underlying does not) in m…
2021-08-03 14:32:23 +03:00
Allan Bowe
5824423c13 fix: showing filerefs that exist (even when underlying does not) in mf_existfileref, along with 3 tests 2021-08-03 14:16:52 +03:00
Allan Bowe
ce5bfd41dc chore(docs): updating header for mm_gettables 2021-08-02 10:37:01 +03:00
51 changed files with 1013 additions and 206 deletions

View File

@@ -39,7 +39,7 @@ jobs:
sudo apt install apt-transport-https
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
sudo apt-key add openvpn-repo-pkg-key.pub
sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-bionic.list
sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-focal.list
sudo apt update
sudo apt install openvpn3

View File

@@ -41,6 +41,13 @@ Documentation: https://core.sasjs.io
- No X command
- Prefixes: _mf_, _mp_
**fcmp** library (SAS9/Viya)
- Function and macro names are identical, except for special cases
- Prefixes: _mcf_
The fcmp macros are used to generate fcmp functions, and can be used with or
without the `proc fcmp` wrapper.
**meta** library (SAS9 only)
- OS independent
@@ -84,7 +91,7 @@ run;
# Installation
First, download the repo to a location your SAS system can access. Then update your sasautos path to include the components you wish to have available,eg:
First, download the repo to a location your SAS system can access. Then update your sasautos path to include the components you wish to have available, eg:
```sas
options insert=(sasautos="/your/path/macrocore/base");
@@ -209,4 +216,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

361
all.sas
View File

@@ -133,7 +133,13 @@ options noquotelenmax;
%macro mf_existfileref(fref
)/*/STORE SOURCE*/;
%if %sysfunc(fileref(&fref))=0 %then %do;
%local rc;
%let rc=%sysfunc(fileref(&fref));
%if &rc=0 %then %do;
1
%end;
%else %if &rc<0 %then %do;
%put &sysmacroname: Fileref &fref exists but the underlying file does not;
1
%end;
%else %do;
@@ -141,6 +147,42 @@ options noquotelenmax;
%end;
%mend mf_existfileref;/**
@file
@brief Checks if a function exists
@details Returns 1 if the function exists, else 0. Note that this function
can be slow as it needs to open the sashelp.vfuncs table.
Usage:
%put %mf_existfunction(CAT);
%put %mf_existfunction(DOG);
Full credit to [Bart](https://sasensei.com/user/305) for the vfunc pointer
and the tidy approach for pure macro data set filtering.
Check out his [SAS Packages](https://github.com/yabwon/SAS_PACKAGES)
framework! Where you can find the same [function](
https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionexists-macro
).
@param [in] name (positional) - function name
@author Allan Bowe
**/
/** @cond */
%macro mf_existfunction(name
)/*/STORE SOURCE*/;
%local dsid rc exist;
%let dsid=%sysfunc(open(sashelp.vfunc(where=(fncname="%upcase(&name)"))));
%let exist=1;
%let exist=%sysfunc(fetch(&dsid, NOSET));
%let rc=%sysfunc(close(&dsid));
%sysevalf(0 = &exist)
%mend mf_existfunction;
/** @endcond *//**
@file
@brief Checks if a variable exists in a data set.
@details Returns 0 if the variable does NOT exist, and return the position of
@@ -233,6 +275,78 @@ options noquotelenmax;
%mend mf_existvarlist;
/** @endcond *//**
@file
@brief Returns the appLoc from the _program variable
@details When working with SASjs apps, web services / tests / jobs are always
deployed to a root (app) location in the SAS logical folder tree.
When building apps for use in other environments, you do not necessarily know
where the backend services will be deployed. Therefore a function like this
is handy in order to dynamically figure out the appLoc, and enable other
services to be connected by a relative reference.
SASjs apps always have the same immediate substructure (one or more of the
following):
@li /data
@li /jobs
@li /services
@li /tests/jobs
@li /tests/services
@li /tests/macros
This function works by testing for the existence of any of the above in the
automatic _program variable, and returning the part to the left of it.
Usage:
%put %mf_getapploc(&_program)
%put %mf_getapploc(/some/location/services/admin/myservice);
%put %mf_getapploc(/some/location/jobs/extract/somejob/);
%put %mf_getapploc(/some/location/tests/jobs/somejob/);
@author Allan Bowe
**/
%macro mf_getapploc(pgm);
%if "&pgm"="" %then %do;
%if %symexist(_program) %then %let pgm=&_program;
%else %do;
%put &sysmacroname: No value provided and no _program variable available;
%return;
%end;
%end;
%local root;
/**
* First check we are not in the tests/macros folder (which has no subfolders)
*/
%if %index(&pgm,/tests/macros/) %then %do;
%let root=%substr(&pgm,1,%index(&pgm,/tests/macros)-1);
&root
%return;
%end;
/**
* Next, move up two levels to avoid matches on subfolder or service name
*/
%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);
%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);
%if %index(&root,/tests/) %then %do;
%let root=%substr(&root,1,%index(&root,/tests/)-1);
%end;
%else %if %index(&root,/services) %then %do;
%let root=%substr(&root,1,%index(&root,/services)-1);
%end;
%else %if %index(&root,/jobs) %then %do;
%let root=%substr(&root,1,%index(&root,/jobs)-1);
%end;
%else %put &sysmacroname: Could not find an app location from &pgm;
&root
%mend mf_getapploc ;/**
@file
@brief Returns a character attribute of a dataset.
@details Can be used in open code, eg as follows:
@@ -8301,10 +8415,10 @@ run;
usage:
%mm_createfolder(path=/some/meta/folder)
%mm_createfolder(path=/some/meta/folder)
@param path= Name of the folder to create.
@param mdebug= set DBG to 1 to disable DEBUG messages
@param [in] path= Name of the folder to create.
@param [in] mdebug= set DBG to 1 to disable DEBUG messages
@version 9.4
@author Allan Bowe
@@ -11665,18 +11779,20 @@ run;
%mend mm_gettableid;/**
@file
@brief Creates a dataset with all metadata tables for a particular library
@details Will only show the tables to which a user has the requisite
metadata access.
@details Will only show the tables for which the executing user has the
requisite metadata access.
usage:
%mm_gettables(uri=A5X8AHW1.B40001S5)
%mm_gettables(uri=A5X8AHW1.B40001S5)
@param outds the dataset to create that contains the list of tables
@param uri the uri of the library for which to return tables
@param getauth= YES|NO - fetch the authdomain used in database connections.
Set to NO to improve runtimes in larger environments, as there can be a
performance hit on the `metadata_getattr(domainuri, "Name", AuthDomain)` call.
@param [in] uri= the uri of the library for which to return tables
@param [out] outds= (work.mm_gettables) the dataset to contain the list of
tables
@param [in] getauth= (YES) Fetch the authdomain used in database connections.
Set to NO to improve runtimes in larger environments, as there can be a
performance hit on the `metadata_getattr(domainuri, "Name", AuthDomain)`
call.
@returns outds dataset containing all groups in a column named "metagroup"
(defaults to work.mm_getlibs). The following columns are provided:
@@ -11704,8 +11820,8 @@ data &outds;
libdesc $200 libref engine $8 IsDBMSLibname $1
tablename $50 /* metadata table names can be longer than $32 */
;
keep libname libdesc libref engine ServerContext path_schema AuthDomain tableuri
tablename IsPreassigned IsDBMSLibname id;
keep libname libdesc libref engine ServerContext path_schema AuthDomain
tableuri tablename IsPreassigned IsDBMSLibname id;
call missing (of _all_);
uri=symget('uri');
@@ -15278,24 +15394,27 @@ filename &fname1 clear;
libname &libref1 clear;
*/
%mend mv_getclients;/**
@file mv_getfoldermembers.sas
@brief Gets a list of folders (and ids) for a given root
@details Works for both root level and below, oauth or password. Default is
oauth, and the token is expected in a global ACCESS_TOKEN variable.
@file
@brief Gets a list of folder members (and ids) for a given root
@details Returns all members for a particular Viya folder. Works at both root
level and below, and results are created in an output dataset.
%mv_getfoldermembers(root=/Public)
%mv_getfoldermembers(root=/Public, outds=work.mymembers)
@param root= The path for which to return the list of folders
@param outds= The output dataset to create (default is work.mv_getfolders). Format:
@param [in] root= (/) The path for which to return the list of folders
@param [out] outds= (work.mv_getfolders) The output dataset to create. Format:
|ordinal_root|ordinal_items|creationTimeStamp| modifiedTimeStamp|createdBy|modifiedBy|id| uri|added| type|name|description|
|---|---|---|---|---|---|---|---|---|---|---|---|
|1|1|2021-05-25T11:15:04.204Z|2021-05-25T11:15:04.204Z|allbow|allbow|4f1e3945-9655-462b-90f2-c31534b3ca47|/folders/folders/ed701ff3-77e8-468d-a4f5-8c43dec0fd9e|2021-05-25T11:15:04.212Z|child|my_folder_name|My folder Description|
@param access_token_var= The global macro variable to contain the access token
@param grant_type= valid values are "password" or "authorization_code" (unquoted).
The default is authorization_code.
@param [in] access_token_var= (ACCESS_TOKEN) The global macro variable to
contain the access token
@param [in] grant_type= (sas_services) Valid values are:
@li password
@li authorization_code
@li detect
@li sas_services
@version VIYA V.03.04
@author Allan Bowe, source: https://github.com/sasjs/core
@@ -15307,6 +15426,12 @@ libname &libref1 clear;
@li mf_getuniquelibref.sas
@li mf_isblank.sas
<h4> Related Macros </h4>
@li mv_createfolder.sas
@li mv_deletefoldermember.sas
@li mv_deleteviyafolder.sas
@li mv_getfoldermembers.test.sas
**/
%macro mv_getfoldermembers(root=/
@@ -15346,7 +15471,7 @@ options noquotelenmax;
%if "&root"="/" %then %do;
/* if root just list root folders */
proc http method='GET' out=&fname1 &oauth_bearer
url="&base_uri/folders/rootFolders";
url="&base_uri/folders/rootFolders?limit=1000";
%if &grant_type=authorization_code %then %do;
headers "Authorization"="Bearer &&&access_token_var";
%end;
@@ -15367,13 +15492,17 @@ options noquotelenmax;
/*data _null_;infile &fname1;input;putlog _infile_;run;*/
libname &libref1 JSON fileref=&fname1;
/* now get the followon link to list members */
%local href;
%let href=0;
%local href cnt;
%let cnt=0;
data _null_;
set &libref1..links;
if rel='members' then call symputx('href',quote("&base_uri"!!trim(href)),'l');
if rel='members' then do;
url=cats("'","&base_uri",href,"?limit=10000'");
call symputx('href',url,'l');
call symputx('cnt',1,'l');
end;
run;
%if &href=0 %then %do;
%if &cnt=0 %then %do;
%put NOTE:;%put NOTE- No members found in &root!!;%put NOTE-;
%return;
%end;
@@ -18736,3 +18865,175 @@ run;
%inc "%sysfunc(pathname(work))/ml_json.lua";
%mend ml_json;
/**
@file
@brief Provides a replacement for the stpsrv_header function
@details The stpsrv_header is normally a built-in function, used to set the
headers for SAS 9 Stored Processes as documented here:
https://go.documentation.sas.com/doc/en/itechcdc/9.4/stpug/srvhead.htm
The purpose of this custom function is to provide a replacement when running
similar code as a web service against
[sasjs/server](https://github.com/sasjs/server). It operates by creating a
text file with the headers. The location of this text file is determined by
a macro variable (`sasjs_stpsrv_header_loc`) which needs to be injected into
each service by the calling process, eg:
%let sasjs_stpsrv_header_loc = C:/temp/some_uuid/stpsrv_header.txt;
Note - the function works by appending headers to the file. If multiple same-
named headers are provided, they will all be appended - the calling process
needs to pick up the last one. This will mean removing the attribute if the
final record has an empty value.
The function takes the following (positional) parameters:
| PARAMETER | DESCRIPTION |
|------------|-------------|
| name $ | name of the header attribute to create|
| value $ | value of the header attribute|
It returns 0 if successful, or -1 if an error occured.
Usage:
%let sasjs_stpsrv_header_loc=%sysfunc(pathname(work))/stpsrv_header.txt;
%mcf_stpsrv_header(wrap=YES, insert_cmplib=YES)
data _null_;
rc=stpsrv_header('Content-type','application/text');
rc=stpsrv_header('Content-disposition',"attachment; filename=file.txt");
run;
data _null_;
infile "&sasjs_stpsrv_header_loc";
input;
putlog _infile_;
run;
@param [out] wrap= (NO) Choose YES to add the proc fcmp wrapper.
@param [out] insert_cmplib= (NO) Choose YES to insert the package into the
CMPLIB reference.
@param [out] lib= (work) The output library in which to create the catalog.
@param [out] cat= (sasjs) The output catalog in which to create the package.
@param [out] pkg= (utils) The output package in which to create the function.
Uses a 3 part format: libref.catalog.package
**/
%macro mcf_stpsrv_header(wrap=NO
,insert_cmplib=NO
,lib=WORK
,cat=SASJS
,pkg=UTILS
)/*/STORE SOURCE*/;
%if &wrap=YES %then %do;
proc fcmp outcat=&lib..&cat..&pkg;
%end;
function stpsrv_header(name $, value $);
length loc $128 val $512;
loc=symget('sasjs_stpsrv_header_loc');
val=trim(name)!!': '!!value;
length fref $8;
rc=filename(fref,loc);
if (rc ne 0) then return( -1 );
fid = fopen(fref,'a');
if (fid = 0) then return( -1 );
rc=fput(fid, val);
rc=fwrite(fid);
rc=fclose(fid);
rc=filename(fref);
return(0);
endsub;
%if &wrap=YES %then %do;
quit;
%end;
%if &insert_cmplib=YES %then %do;
options insert=(CMPLIB=(&lib..&cat));
%end;
%mend mcf_stpsrv_header;/**
@file
@brief Adds a string to a file
@details Creates an fcmp function for appending a string to an external file.
If the file does not exist, it is created.
The function itself takes the following (positional) parameters:
| PARAMETER | DESCRIPTION |
|------------|-------------|
| filepath $ | full path to the file|
| string $ | string to add to the file |
| mode $ | mode of the output - either APPEND (default) or CREATE |
It returns 0 if successful, or -1 if an error occured.
Usage:
%mcf_string2file(wrap=YES, insert_cmplib=YES)
data _null_;
rc=mcf_string2file(
"%sysfunc(pathname(work))/newfile.txt"
, "This is a test"
, "CREATE");
run;
data _null_;
infile "%sysfunc(pathname(work))/newfile.txt";
input;
putlog _infile_;
run;
@param [out] wrap= (NO) Choose YES to add the proc fcmp wrapper.
@param [out] insert_cmplib= (NO) Choose YES to insert the package into the
CMPLIB reference.
@param [out] lib= (work) The output library in which to create the catalog.
@param [out] cat= (sasjs) The output catalog in which to create the package.
@param [out] pkg= (utils) The output package in which to create the function.
Uses a 3 part format: libref.catalog.package
**/
%macro mcf_string2file(wrap=NO
,insert_cmplib=NO
,lib=WORK
,cat=SASJS
,pkg=UTILS
)/*/STORE SOURCE*/;
%if &wrap=YES %then %do;
proc fcmp outcat=&lib..&cat..&pkg;
%end;
function mcf_string2file(filepath $, string $, mode $);
if mode='APPEND' then fmode='a';
else fmode='o';
length fref $8;
rc=filename(fref,filepath);
if (rc ne 0) then return( -1 );
fid = fopen(fref,fmode);
if (fid = 0) then return( -1 );
rc=fput(fid, string);
rc=fwrite(fid);
rc=fclose(fid);
rc=filename(fref);
return(0);
endsub;
%if &wrap=YES %then %do;
quit;
%end;
%if &insert_cmplib=YES %then %do;
options insert=(CMPLIB=(&lib..&cat));
%end;
%mend mcf_string2file;

View File

@@ -17,7 +17,13 @@
%macro mf_existfileref(fref
)/*/STORE SOURCE*/;
%if %sysfunc(fileref(&fref))=0 %then %do;
%local rc;
%let rc=%sysfunc(fileref(&fref));
%if &rc=0 %then %do;
1
%end;
%else %if &rc<0 %then %do;
%put &sysmacroname: Fileref &fref exists but the underlying file does not;
1
%end;
%else %do;

37
base/mf_existfunction.sas Normal file
View File

@@ -0,0 +1,37 @@
/**
@file
@brief Checks if a function exists
@details Returns 1 if the function exists, else 0. Note that this function
can be slow as it needs to open the sashelp.vfuncs table.
Usage:
%put %mf_existfunction(CAT);
%put %mf_existfunction(DOG);
Full credit to [Bart](https://sasensei.com/user/305) for the vfunc pointer
and the tidy approach for pure macro data set filtering.
Check out his [SAS Packages](https://github.com/yabwon/SAS_PACKAGES)
framework! Where you can find the same [function](
https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionexists-macro
).
@param [in] name (positional) - function name
@author Allan Bowe
**/
/** @cond */
%macro mf_existfunction(name
)/*/STORE SOURCE*/;
%local dsid rc exist;
%let dsid=%sysfunc(open(sashelp.vfunc(where=(fncname="%upcase(&name)"))));
%let exist=1;
%let exist=%sysfunc(fetch(&dsid, NOSET));
%let rc=%sysfunc(close(&dsid));
%sysevalf(0 = &exist)
%mend mf_existfunction;
/** @endcond */

73
base/mf_getapploc.sas Normal file
View File

@@ -0,0 +1,73 @@
/**
@file
@brief Returns the appLoc from the _program variable
@details When working with SASjs apps, web services / tests / jobs are always
deployed to a root (app) location in the SAS logical folder tree.
When building apps for use in other environments, you do not necessarily know
where the backend services will be deployed. Therefore a function like this
is handy in order to dynamically figure out the appLoc, and enable other
services to be connected by a relative reference.
SASjs apps always have the same immediate substructure (one or more of the
following):
@li /data
@li /jobs
@li /services
@li /tests/jobs
@li /tests/services
@li /tests/macros
This function works by testing for the existence of any of the above in the
automatic _program variable, and returning the part to the left of it.
Usage:
%put %mf_getapploc(&_program)
%put %mf_getapploc(/some/location/services/admin/myservice);
%put %mf_getapploc(/some/location/jobs/extract/somejob/);
%put %mf_getapploc(/some/location/tests/jobs/somejob/);
@author Allan Bowe
**/
%macro mf_getapploc(pgm);
%if "&pgm"="" %then %do;
%if %symexist(_program) %then %let pgm=&_program;
%else %do;
%put &sysmacroname: No value provided and no _program variable available;
%return;
%end;
%end;
%local root;
/**
* First check we are not in the tests/macros folder (which has no subfolders)
*/
%if %index(&pgm,/tests/macros/) %then %do;
%let root=%substr(&pgm,1,%index(&pgm,/tests/macros)-1);
&root
%return;
%end;
/**
* Next, move up two levels to avoid matches on subfolder or service name
*/
%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);
%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);
%if %index(&root,/tests/) %then %do;
%let root=%substr(&root,1,%index(&root,/tests/)-1);
%end;
%else %if %index(&root,/services) %then %do;
%let root=%substr(&root,1,%index(&root,/services)-1);
%end;
%else %if %index(&root,/jobs) %then %do;
%let root=%substr(&root,1,%index(&root,/jobs)-1);
%end;
%else %put &sysmacroname: Could not find an app location from &pgm;
&root
%mend mf_getapploc ;

View File

@@ -84,7 +84,7 @@ options noquotelenmax;
"""
f = open('all.sas', "w") # r / r+ / rb / rb+ / w / wb
f.write(header)
folders=['base','meta','metax','viya','lua']
folders=['base','meta','metax','viya','lua','fcmp']
for folder in folders:
filenames = [fn for fn in Path('./' + folder).iterdir() if fn.match("*.sas")]
filenames.sort()

View File

@@ -0,0 +1,94 @@
/**
@file
@brief Provides a replacement for the stpsrv_header function
@details The stpsrv_header is normally a built-in function, used to set the
headers for SAS 9 Stored Processes as documented here:
https://go.documentation.sas.com/doc/en/itechcdc/9.4/stpug/srvhead.htm
The purpose of this custom function is to provide a replacement when running
similar code as a web service against
[sasjs/server](https://github.com/sasjs/server). It operates by creating a
text file with the headers. The location of this text file is determined by
a macro variable (`sasjs_stpsrv_header_loc`) which needs to be injected into
each service by the calling process, eg:
%let sasjs_stpsrv_header_loc = C:/temp/some_uuid/stpsrv_header.txt;
Note - the function works by appending headers to the file. If multiple same-
named headers are provided, they will all be appended - the calling process
needs to pick up the last one. This will mean removing the attribute if the
final record has an empty value.
The function takes the following (positional) parameters:
| PARAMETER | DESCRIPTION |
|------------|-------------|
| name $ | name of the header attribute to create|
| value $ | value of the header attribute|
It returns 0 if successful, or -1 if an error occured.
Usage:
%let sasjs_stpsrv_header_loc=%sysfunc(pathname(work))/stpsrv_header.txt;
%mcf_stpsrv_header(wrap=YES, insert_cmplib=YES)
data _null_;
rc=stpsrv_header('Content-type','application/text');
rc=stpsrv_header('Content-disposition',"attachment; filename=file.txt");
run;
data _null_;
infile "&sasjs_stpsrv_header_loc";
input;
putlog _infile_;
run;
@param [out] wrap= (NO) Choose YES to add the proc fcmp wrapper.
@param [out] insert_cmplib= (NO) Choose YES to insert the package into the
CMPLIB reference.
@param [out] lib= (work) The output library in which to create the catalog.
@param [out] cat= (sasjs) The output catalog in which to create the package.
@param [out] pkg= (utils) The output package in which to create the function.
Uses a 3 part format: libref.catalog.package
**/
%macro mcf_stpsrv_header(wrap=NO
,insert_cmplib=NO
,lib=WORK
,cat=SASJS
,pkg=UTILS
)/*/STORE SOURCE*/;
%if &wrap=YES %then %do;
proc fcmp outcat=&lib..&cat..&pkg;
%end;
function stpsrv_header(name $, value $);
length loc $128 val $512;
loc=symget('sasjs_stpsrv_header_loc');
val=trim(name)!!': '!!value;
length fref $8;
rc=filename(fref,loc);
if (rc ne 0) then return( -1 );
fid = fopen(fref,'a');
if (fid = 0) then return( -1 );
rc=fput(fid, val);
rc=fwrite(fid);
rc=fclose(fid);
rc=filename(fref);
return(0);
endsub;
%if &wrap=YES %then %do;
quit;
%end;
%if &insert_cmplib=YES %then %do;
options insert=(CMPLIB=(&lib..&cat));
%end;
%mend mcf_stpsrv_header;

79
fcmp/mcf_string2file.sas Normal file
View File

@@ -0,0 +1,79 @@
/**
@file
@brief Adds a string to a file
@details Creates an fcmp function for appending a string to an external file.
If the file does not exist, it is created.
The function itself takes the following (positional) parameters:
| PARAMETER | DESCRIPTION |
|------------|-------------|
| filepath $ | full path to the file|
| string $ | string to add to the file |
| mode $ | mode of the output - either APPEND (default) or CREATE |
It returns 0 if successful, or -1 if an error occured.
Usage:
%mcf_string2file(wrap=YES, insert_cmplib=YES)
data _null_;
rc=mcf_string2file(
"%sysfunc(pathname(work))/newfile.txt"
, "This is a test"
, "CREATE");
run;
data _null_;
infile "%sysfunc(pathname(work))/newfile.txt";
input;
putlog _infile_;
run;
@param [out] wrap= (NO) Choose YES to add the proc fcmp wrapper.
@param [out] insert_cmplib= (NO) Choose YES to insert the package into the
CMPLIB reference.
@param [out] lib= (work) The output library in which to create the catalog.
@param [out] cat= (sasjs) The output catalog in which to create the package.
@param [out] pkg= (utils) The output package in which to create the function.
Uses a 3 part format: libref.catalog.package
**/
%macro mcf_string2file(wrap=NO
,insert_cmplib=NO
,lib=WORK
,cat=SASJS
,pkg=UTILS
)/*/STORE SOURCE*/;
%if &wrap=YES %then %do;
proc fcmp outcat=&lib..&cat..&pkg;
%end;
function mcf_string2file(filepath $, string $, mode $);
if mode='APPEND' then fmode='a';
else fmode='o';
length fref $8;
rc=filename(fref,filepath);
if (rc ne 0) then return( -1 );
fid = fopen(fref,fmode);
if (fid = 0) then return( -1 );
rc=fput(fid, string);
rc=fwrite(fid);
rc=fclose(fid);
rc=filename(fref);
return(0);
endsub;
%if &wrap=YES %then %do;
quit;
%end;
%if &insert_cmplib=YES %then %do;
options insert=(CMPLIB=(&lib..&cat));
%end;
%mend mcf_string2file;

View File

@@ -20,6 +20,19 @@
*/
/*! \dir fcmp
* \brief Macros for generating FCMP functions
* \details These macros have the following attributes:
* Macro name matches compiled function / subroutine name
* Prefixes: _mcf_, _mcs_
The macro part is just a wrapper for the underlying function / subroutine,
and has switches for including the proc fcmp / quit statements and whether
to insert the package into the CMPLIB option.
*/
/*! \dir meta
* \brief Metadata Aware Macros
* \details These macros have the following attributes:

View File

@@ -14,10 +14,10 @@
usage:
%mm_createfolder(path=/some/meta/folder)
%mm_createfolder(path=/some/meta/folder)
@param path= Name of the folder to create.
@param mdebug= set DBG to 1 to disable DEBUG messages
@param [in] path= Name of the folder to create.
@param [in] mdebug= set DBG to 1 to disable DEBUG messages
@version 9.4
@author Allan Bowe

View File

@@ -1,18 +1,20 @@
/**
@file
@brief Creates a dataset with all metadata tables for a particular library
@details Will only show the tables to which a user has the requisite
metadata access.
@details Will only show the tables for which the executing user has the
requisite metadata access.
usage:
%mm_gettables(uri=A5X8AHW1.B40001S5)
%mm_gettables(uri=A5X8AHW1.B40001S5)
@param outds the dataset to create that contains the list of tables
@param uri the uri of the library for which to return tables
@param getauth= YES|NO - fetch the authdomain used in database connections.
Set to NO to improve runtimes in larger environments, as there can be a
performance hit on the `metadata_getattr(domainuri, "Name", AuthDomain)` call.
@param [in] uri= the uri of the library for which to return tables
@param [out] outds= (work.mm_gettables) the dataset to contain the list of
tables
@param [in] getauth= (YES) Fetch the authdomain used in database connections.
Set to NO to improve runtimes in larger environments, as there can be a
performance hit on the `metadata_getattr(domainuri, "Name", AuthDomain)`
call.
@returns outds dataset containing all groups in a column named "metagroup"
(defaults to work.mm_getlibs). The following columns are provided:
@@ -40,8 +42,8 @@ data &outds;
libdesc $200 libref engine $8 IsDBMSLibname $1
tablename $50 /* metadata table names can be longer than $32 */
;
keep libname libdesc libref engine ServerContext path_schema AuthDomain tableuri
tablename IsPreassigned IsDBMSLibname id;
keep libname libdesc libref engine ServerContext path_schema AuthDomain
tableuri tablename IsPreassigned IsDBMSLibname id;
call missing (of _all_);
uri=symget('uri');

226
package-lock.json generated
View File

@@ -7,44 +7,43 @@
"name": "@sasjs/core",
"license": "MIT",
"devDependencies": {
"@sasjs/cli": "2.33.3"
"@sasjs/cli": "^2.37.2"
}
},
"node_modules/@sasjs/adapter": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-2.8.9.tgz",
"integrity": "sha512-8UChcZlqqlmaMMaKOCr2Bc1h+i2KVDY0FINlPXQN5PdAgEMd8dbxI2p9bsiI1yjYjOBO9LuMl7B79/mwYCtyEw==",
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-2.10.0.tgz",
"integrity": "sha512-GbvyIgbODnAJaBaz/Tz8/IwcujNOsZcwzbIuKQtG5y13BzgVPtx/e7b2TAhdoBqd+8uVh0CdrSDfOV/SuvPurg==",
"dev": true,
"dependencies": {
"@sasjs/utils": "^2.21.0",
"@sasjs/utils": "^2.27.1",
"axios": "^0.21.1",
"axios-cookiejar-support": "^1.0.1",
"form-data": "^4.0.0",
"https": "^1.0.0",
"jwt-decode": "^3.1.2",
"tough-cookie": "^4.0.0",
"url": "^0.11.0"
"tough-cookie": "^4.0.0"
},
"engines": {
"node": ">=15"
}
},
"node_modules/@sasjs/cli": {
"version": "2.33.3",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-2.33.3.tgz",
"integrity": "sha512-y1uFM5MEE6eoKLrPUJbweDt4njSy9Y3CS5w5/U2xwNbiAyhiPXgkwCHjCQ8Qg+0rQnMvyNEn6qJuJZ3udc6T7w==",
"version": "2.37.2",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-2.37.2.tgz",
"integrity": "sha512-Bcm8UFN9Y/ZON4T31Gu9mf1REn1pZoStHFVrix/yp7mchFt5rrrY5RbIqO/AI1jzhShSVg9P7oU4VPJrqki+SA==",
"dev": true,
"dependencies": {
"@sasjs/adapter": "2.8.9",
"@sasjs/core": "2.35.3",
"@sasjs/adapter": "2.10.0",
"@sasjs/core": "2.35.4",
"@sasjs/lint": "1.11.2",
"@sasjs/utils": "2.23.3",
"@types/url-parse": "1.4.3",
"btoa": "1.2.1",
"@sasjs/utils": "2.27.1",
"chalk": "4.1.1",
"csv-stringify": "5.6.2",
"dotenv": "10.0.0",
"esm": "3.2.25",
"find": "0.3.0",
"fs-extra": "10.0.0",
"get-installed-path": "4.0.8",
"js-base64": "3.6.1",
"jsdom": "16.6.0",
"jwt-decode": "3.1.2",
"lodash.groupby": "4.6.0",
@@ -60,9 +59,9 @@
}
},
"node_modules/@sasjs/core": {
"version": "2.35.3",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-2.35.3.tgz",
"integrity": "sha512-3o5PU6DkihpA+Aibt1lRy4USqJI0VFa+wNsKCD+bUD2DLZICU3JablZQxwAPH70VWJGXAUJtDFj0T/iRo5Devg==",
"version": "2.35.4",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-2.35.4.tgz",
"integrity": "sha512-Exr3+yRdvacKvbXQoi1RfGHi5NtZUkc7RwFNkemHTFXLYaIzPI8CGSCaQmKkwM1UteOJly2e2pw2YT6kHNY1NA==",
"dev": true
},
"node_modules/@sasjs/lint": {
@@ -75,11 +74,12 @@
}
},
"node_modules/@sasjs/utils": {
"version": "2.23.3",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.23.3.tgz",
"integrity": "sha512-tEh4mGG80eUxSLpbPivA0vl4akMdauL+yZrLn1uUM8EyiXPvlcWPkQTeN6oHbyyAH108D9cfEBidTePZh1p5VQ==",
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.27.1.tgz",
"integrity": "sha512-CYTQwEj89cc7H3tGiQQcyDkZYaWRc1HZJpOF8o2RHYS37fIAOy0SyyJdq6mcQ74Nb1u5AmFXPFIvnRCMEcTYeQ==",
"dev": true,
"dependencies": {
"@types/fs-extra": "^9.0.11",
"@types/prompts": "^2.0.13",
"chalk": "^4.1.1",
"cli-table": "^0.3.6",
@@ -87,7 +87,11 @@
"fs-extra": "^10.0.0",
"jwt-decode": "^3.1.2",
"prompts": "^2.4.1",
"rimraf": "^3.0.2",
"valid-url": "^1.0.9"
},
"engines": {
"node": ">=15"
}
},
"node_modules/@tootallnate/once": {
@@ -99,6 +103,15 @@
"node": ">= 6"
}
},
"node_modules/@types/fs-extra": {
"version": "9.0.12",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.12.tgz",
"integrity": "sha512-I+bsBr67CurCGnSenZZ7v94gd3tc3+Aj2taxMT4yu4ABLuOgOjeFxX3dokG24ztSRg5tnT00sL8BszO7gSMoIw==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "16.3.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.3.1.tgz",
@@ -121,12 +134,6 @@
"dev": true,
"peer": true
},
"node_modules/@types/url-parse": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@types/url-parse/-/url-parse-1.4.3.tgz",
"integrity": "sha512-4kHAkbV/OfW2kb5BLVUuUMoumB3CP8rHqlw48aHvFy5tf9ER0AfOonBlX29l/DD68G70DmyhRlSYfQPSYpC5Vw==",
"dev": true
},
"node_modules/abab": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
@@ -298,18 +305,6 @@
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
"dev": true
},
"node_modules/btoa": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
"dev": true,
"bin": {
"btoa": "bin/btoa.js"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -657,9 +652,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
"integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==",
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz",
"integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==",
"dev": true,
"funding": [
{
@@ -990,6 +985,12 @@
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"node_modules/js-base64": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.6.1.tgz",
"integrity": "sha512-Frdq2+tRRGLQUIQOgsIGSCd1VePCS2fsddTG5dTCqR0JHgltXWfsxnY0gIXPoMeRmdom6Oyq+UMOFg5suduOjQ==",
"dev": true
},
"node_modules/jsdom": {
"version": "16.6.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.6.0.tgz",
@@ -1273,9 +1274,9 @@
}
},
"node_modules/path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"node_modules/pify": {
@@ -1327,16 +1328,6 @@
"node": ">=6"
}
},
"node_modules/querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
"deprecated": "The",
"dev": true,
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@@ -1595,22 +1586,6 @@
"node": ">= 10.0.0"
}
},
"node_modules/url": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
"integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
"dev": true,
"dependencies": {
"punycode": "1.3.2",
"querystring": "0.2.0"
}
},
"node_modules/url/node_modules/punycode": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
"dev": true
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -1760,40 +1735,36 @@
},
"dependencies": {
"@sasjs/adapter": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-2.8.9.tgz",
"integrity": "sha512-8UChcZlqqlmaMMaKOCr2Bc1h+i2KVDY0FINlPXQN5PdAgEMd8dbxI2p9bsiI1yjYjOBO9LuMl7B79/mwYCtyEw==",
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-2.10.0.tgz",
"integrity": "sha512-GbvyIgbODnAJaBaz/Tz8/IwcujNOsZcwzbIuKQtG5y13BzgVPtx/e7b2TAhdoBqd+8uVh0CdrSDfOV/SuvPurg==",
"dev": true,
"requires": {
"@sasjs/utils": "^2.21.0",
"@sasjs/utils": "^2.27.1",
"axios": "^0.21.1",
"axios-cookiejar-support": "^1.0.1",
"form-data": "^4.0.0",
"https": "^1.0.0",
"jwt-decode": "^3.1.2",
"tough-cookie": "^4.0.0",
"url": "^0.11.0"
"tough-cookie": "^4.0.0"
}
},
"@sasjs/cli": {
"version": "2.33.3",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-2.33.3.tgz",
"integrity": "sha512-y1uFM5MEE6eoKLrPUJbweDt4njSy9Y3CS5w5/U2xwNbiAyhiPXgkwCHjCQ8Qg+0rQnMvyNEn6qJuJZ3udc6T7w==",
"version": "2.37.2",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-2.37.2.tgz",
"integrity": "sha512-Bcm8UFN9Y/ZON4T31Gu9mf1REn1pZoStHFVrix/yp7mchFt5rrrY5RbIqO/AI1jzhShSVg9P7oU4VPJrqki+SA==",
"dev": true,
"requires": {
"@sasjs/adapter": "2.8.9",
"@sasjs/core": "2.35.3",
"@sasjs/adapter": "2.10.0",
"@sasjs/core": "2.35.4",
"@sasjs/lint": "1.11.2",
"@sasjs/utils": "2.23.3",
"@types/url-parse": "1.4.3",
"btoa": "1.2.1",
"@sasjs/utils": "2.27.1",
"chalk": "4.1.1",
"csv-stringify": "5.6.2",
"dotenv": "10.0.0",
"esm": "3.2.25",
"find": "0.3.0",
"fs-extra": "10.0.0",
"get-installed-path": "4.0.8",
"js-base64": "3.6.1",
"jsdom": "16.6.0",
"jwt-decode": "3.1.2",
"lodash.groupby": "4.6.0",
@@ -1806,9 +1777,9 @@
}
},
"@sasjs/core": {
"version": "2.35.3",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-2.35.3.tgz",
"integrity": "sha512-3o5PU6DkihpA+Aibt1lRy4USqJI0VFa+wNsKCD+bUD2DLZICU3JablZQxwAPH70VWJGXAUJtDFj0T/iRo5Devg==",
"version": "2.35.4",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-2.35.4.tgz",
"integrity": "sha512-Exr3+yRdvacKvbXQoi1RfGHi5NtZUkc7RwFNkemHTFXLYaIzPI8CGSCaQmKkwM1UteOJly2e2pw2YT6kHNY1NA==",
"dev": true
},
"@sasjs/lint": {
@@ -1821,11 +1792,12 @@
}
},
"@sasjs/utils": {
"version": "2.23.3",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.23.3.tgz",
"integrity": "sha512-tEh4mGG80eUxSLpbPivA0vl4akMdauL+yZrLn1uUM8EyiXPvlcWPkQTeN6oHbyyAH108D9cfEBidTePZh1p5VQ==",
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.27.1.tgz",
"integrity": "sha512-CYTQwEj89cc7H3tGiQQcyDkZYaWRc1HZJpOF8o2RHYS37fIAOy0SyyJdq6mcQ74Nb1u5AmFXPFIvnRCMEcTYeQ==",
"dev": true,
"requires": {
"@types/fs-extra": "^9.0.11",
"@types/prompts": "^2.0.13",
"chalk": "^4.1.1",
"cli-table": "^0.3.6",
@@ -1833,6 +1805,7 @@
"fs-extra": "^10.0.0",
"jwt-decode": "^3.1.2",
"prompts": "^2.4.1",
"rimraf": "^3.0.2",
"valid-url": "^1.0.9"
}
},
@@ -1842,6 +1815,15 @@
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
"dev": true
},
"@types/fs-extra": {
"version": "9.0.12",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.12.tgz",
"integrity": "sha512-I+bsBr67CurCGnSenZZ7v94gd3tc3+Aj2taxMT4yu4ABLuOgOjeFxX3dokG24ztSRg5tnT00sL8BszO7gSMoIw==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/node": {
"version": "16.3.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.3.1.tgz",
@@ -1864,12 +1846,6 @@
"dev": true,
"peer": true
},
"@types/url-parse": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@types/url-parse/-/url-parse-1.4.3.tgz",
"integrity": "sha512-4kHAkbV/OfW2kb5BLVUuUMoumB3CP8rHqlw48aHvFy5tf9ER0AfOonBlX29l/DD68G70DmyhRlSYfQPSYpC5Vw==",
"dev": true
},
"abab": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
@@ -1994,12 +1970,6 @@
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
"dev": true
},
"btoa": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
"dev": true
},
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -2253,9 +2223,9 @@
}
},
"follow-redirects": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
"integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==",
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz",
"integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==",
"dev": true
},
"form-data": {
@@ -2493,6 +2463,12 @@
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"js-base64": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.6.1.tgz",
"integrity": "sha512-Frdq2+tRRGLQUIQOgsIGSCd1VePCS2fsddTG5dTCqR0JHgltXWfsxnY0gIXPoMeRmdom6Oyq+UMOFg5suduOjQ==",
"dev": true
},
"jsdom": {
"version": "16.6.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.6.0.tgz",
@@ -2717,9 +2693,9 @@
"dev": true
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"pify": {
@@ -2756,12 +2732,6 @@
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
"dev": true
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@@ -2954,24 +2924,6 @@
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"dev": true
},
"url": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
"integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
"dev": true,
"requires": {
"punycode": "1.3.2",
"querystring": "0.2.0"
},
"dependencies": {
"punycode": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
"dev": true
}
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@@ -27,12 +27,12 @@
"main": "index.js",
"scripts": {
"build": "sasjs cbd -t viya",
"docs": "sasjs doc && ./sasjs/utils/build.sh",
"docs": "sasjs doc -t docsonly && ./sasjs/utils/build.sh",
"test": "sasjs test -t viya",
"lint": "sasjs lint",
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks || true"
},
"devDependencies": {
"@sasjs/cli": "2.33.3"
"@sasjs/cli": "^2.37.2"
}
}

View File

@@ -2,11 +2,12 @@
"$schema": "https://cli.sasjs.io/sasjsconfig-schema.json",
"macroFolders": [
"base",
"fcmp",
"meta",
"metax",
"viya",
"lua",
"tests/base"
"tests/crossplatform"
],
"docConfig": {
"displayMacroCore": false,
@@ -33,7 +34,7 @@
"allowInsecureRequests": false,
"appLoc": "/Public/temp/macrocore",
"macroFolders": [
"tests/viya"
"tests/viyaonly"
],
"programFolders": [],
"deployConfig": {
@@ -48,7 +49,15 @@
"serverType": "SAS9",
"appLoc": "/Shared Data/temp/macrocore",
"macroFolders": [
"tests/meta"
"tests/sas9only"
]
},
{
"name": "docsonly",
"serverType": "SAS9",
"macroFolders": [
"tests/sas9only",
"tests/viyaonly"
]
}
]

View File

@@ -0,0 +1,39 @@
/**
@file
@brief Testing mcf_stpsrv_header macro
<h4> SAS Macros </h4>
@li mcf_stpsrv_header.sas
@li mp_assert.sas
**/
%let sasjs_stpsrv_header_loc=%sysfunc(pathname(work))/stpsrv_header.txt;
%mcf_stpsrv_header(wrap=YES, insert_cmplib=YES)
data _null_;
rc=stpsrv_header('Content-type','application/text');
rc=stpsrv_header('Content-disposition',"attachment; filename=file.txt");
run;
%let test1=FAIL;
%let test2=FAIL;
data _null_;
infile "&sasjs_stpsrv_header_loc";
input;
if _n_=1 and _infile_='Content-type: application/text'
then call symputx('test1','PASS');
else if _n_=2 & _infile_='Content-disposition: attachment; filename=file.txt'
then call symputx('test2','PASS');
run;
%mp_assert(
iftrue=(%str(&test1)=%str(PASS)),
desc=Check first header line
)
%mp_assert(
iftrue=(%str(&test2)=%str(PASS)),
desc=Check second header line
)

View File

@@ -0,0 +1,52 @@
/**
@file
@brief Testing mcf_string2file macro
<h4> SAS Macros </h4>
@li mcf_string2file.sas
@li mp_assert.sas
**/
%mcf_string2file(wrap=YES, insert_cmplib=YES)
data _null_;
rc=mcf_string2file(
"%sysfunc(pathname(work))/newfile.txt"
, "line1"
, "APPEND");
rc=mcf_string2file(
"%sysfunc(pathname(work))/newfile.txt"
, "line2"
, "APPEND");
run;
data _null_;
infile "%sysfunc(pathname(work))/newfile.txt";
input;
if _n_=2 then call symputx('val',_infile_);
run;
%mp_assert(
iftrue=(%str(&val)=%str(line2)),
desc=Check if APPEND works
)
data _null_;
rc=mcf_string2file(
"%sysfunc(pathname(work))/newfile.txt"
, "creating"
, "CREATE");
run;
data _null_;
infile "%sysfunc(pathname(work))/newfile.txt";
input;
if _n_=1 then call symputx('val2',_infile_);
run;
%mp_assert(
iftrue=(%str(&val2)=%str(creating)),
desc=Check if CREATE works
)

View File

@@ -0,0 +1,35 @@
/**
@file
@brief Testing mf_existfileref macro
<h4> SAS Macros </h4>
@li mf_existfileref.sas
@li mp_assert.sas
**/
filename ref1 temp;
filename ref2 temp;
data _null_;
file ref1;
put 'exists';
run;
%mp_assert(
iftrue=(%mf_existfileref(ref1)=1),
desc=Checking fileref WITH target file exists,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_existfileref(ref2)=1),
desc=Checking fileref WITHOUT target file exists,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_existfileref(ref3)=0),
desc=Checking non-existant fref does not exist,
outds=work.test_results
)

View File

@@ -0,0 +1,22 @@
/**
@file
@brief Testing mf_existfunction macro
<h4> SAS Macros </h4>
@li mf_existfunction.sas
@li mp_assert.sas
**/
%mp_assert(
iftrue=(%mf_existfunction(CAT)=1),
desc=Checking if CAT function exists,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_existfunction(DOG)=0),
desc=Checking DOG function does not exist,
outds=work.test_results
)

View File

@@ -0,0 +1,49 @@
/**
@file
@brief Testing mf_getapploc macro
<h4> SAS Macros </h4>
@li mf_getapploc.sas
@li mp_assert.sas
**/
%mp_assert(
iftrue=(
"%mf_getapploc(/some/loc/tests/services/x/service)"="/some/loc"
),
desc=Checking test appLoc matches,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_getapploc(/some/loc/tests/services/tests/service)"="/some/loc"
),
desc=Checking nested services appLoc matches,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_getapploc(/some/area/services/admin/service)"="/some/area"
),
desc=Checking services appLoc matches,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_getapploc(/some/area/jobs/jobs/job)"="/some/area"
),
desc=Checking jobs appLoc matches,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_getapploc(/some/area/tests/macros/somemacro.sas)"="/some/area"
),
desc=Checking tests/macros appLoc matches (which has no subfolder),
outds=work.test_results
)

View File

@@ -0,0 +1,24 @@
/**
@file
@brief Testing mv_getfoldermembers macro
@details Testing is performed by ensuring that the tests/macros folder
contains more than 20 results (which also means the default was increased)
<h4> SAS Macros </h4>
@li mf_getapploc.sas
@li mp_assertdsobs.sas
@li mv_getfoldermembers.sas
**/
options mprint;
%mv_getfoldermembers(
root=%mf_getapploc()/tests/macros,
outds=work.results
)
%mp_assertdsobs(work.results,
desc=%str(Ensuring over 20 results found in %mf_getapploc()/tests/macros),
test=ATLEAST 21,
outds=work.test_results
)

View File

@@ -1,22 +1,25 @@
/**
@file mv_getfoldermembers.sas
@brief Gets a list of folders (and ids) for a given root
@details Works for both root level and below, oauth or password. Default is
oauth, and the token is expected in a global ACCESS_TOKEN variable.
@file
@brief Gets a list of folder members (and ids) for a given root
@details Returns all members for a particular Viya folder. Works at both root
level and below, and results are created in an output dataset.
%mv_getfoldermembers(root=/Public)
%mv_getfoldermembers(root=/Public, outds=work.mymembers)
@param root= The path for which to return the list of folders
@param outds= The output dataset to create (default is work.mv_getfolders). Format:
@param [in] root= (/) The path for which to return the list of folders
@param [out] outds= (work.mv_getfolders) The output dataset to create. Format:
|ordinal_root|ordinal_items|creationTimeStamp| modifiedTimeStamp|createdBy|modifiedBy|id| uri|added| type|name|description|
|---|---|---|---|---|---|---|---|---|---|---|---|
|1|1|2021-05-25T11:15:04.204Z|2021-05-25T11:15:04.204Z|allbow|allbow|4f1e3945-9655-462b-90f2-c31534b3ca47|/folders/folders/ed701ff3-77e8-468d-a4f5-8c43dec0fd9e|2021-05-25T11:15:04.212Z|child|my_folder_name|My folder Description|
@param access_token_var= The global macro variable to contain the access token
@param grant_type= valid values are "password" or "authorization_code" (unquoted).
The default is authorization_code.
@param [in] access_token_var= (ACCESS_TOKEN) The global macro variable to
contain the access token
@param [in] grant_type= (sas_services) Valid values are:
@li password
@li authorization_code
@li detect
@li sas_services
@version VIYA V.03.04
@author Allan Bowe, source: https://github.com/sasjs/core
@@ -28,6 +31,12 @@
@li mf_getuniquelibref.sas
@li mf_isblank.sas
<h4> Related Macros </h4>
@li mv_createfolder.sas
@li mv_deletefoldermember.sas
@li mv_deleteviyafolder.sas
@li mv_getfoldermembers.test.sas
**/
%macro mv_getfoldermembers(root=/
@@ -67,7 +76,7 @@ options noquotelenmax;
%if "&root"="/" %then %do;
/* if root just list root folders */
proc http method='GET' out=&fname1 &oauth_bearer
url="&base_uri/folders/rootFolders";
url="&base_uri/folders/rootFolders?limit=1000";
%if &grant_type=authorization_code %then %do;
headers "Authorization"="Bearer &&&access_token_var";
%end;
@@ -88,13 +97,17 @@ options noquotelenmax;
/*data _null_;infile &fname1;input;putlog _infile_;run;*/
libname &libref1 JSON fileref=&fname1;
/* now get the followon link to list members */
%local href;
%let href=0;
%local href cnt;
%let cnt=0;
data _null_;
set &libref1..links;
if rel='members' then call symputx('href',quote("&base_uri"!!trim(href)),'l');
if rel='members' then do;
url=cats("'","&base_uri",href,"?limit=10000'");
call symputx('href',url,'l');
call symputx('cnt',1,'l');
end;
run;
%if &href=0 %then %do;
%if &cnt=0 %then %do;
%put NOTE:;%put NOTE- No members found in &root!!;%put NOTE-;
%return;
%end;