mirror of
https://github.com/sasjs/core.git
synced 2025-12-11 06:24:35 +00:00
Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b2b81a501 | ||
|
|
02de4e42bf | ||
|
|
ddd831fe7a | ||
|
|
42a46b32e9 | ||
|
|
3b395b3ae5 | ||
|
|
9e84e47563 | ||
|
|
aff29427e2 | ||
|
|
474f1b3cc6 | ||
|
|
22bf8065bb | ||
|
|
7bb089e269 | ||
|
|
a6e9158814 | ||
|
|
19a1336720 | ||
|
|
79d5d42e6b | ||
|
|
2d81de5841 | ||
|
|
107ab836d6 | ||
|
|
5225e74465 | ||
|
|
39253d2828 | ||
|
|
171c169537 | ||
|
|
76af9fa33c | ||
|
|
37ccc8a2aa | ||
|
|
d0a0274990 | ||
|
|
b3d374f1b1 | ||
|
|
1c4458faf6 | ||
|
|
96e1d146f4 | ||
|
|
aadc4fb83d | ||
|
|
988ee89cdb | ||
|
|
51cbfbf4bc | ||
|
|
4b69e91362 | ||
|
|
8f9715035a | ||
|
|
35ddccaa16 | ||
|
|
cb0ddfb61c | ||
|
|
c3b6f06b3a | ||
|
|
8046d5a0b1 | ||
|
|
aed07f2943 | ||
|
|
5bf87a78b8 | ||
|
|
0851523d18 | ||
|
|
9e2de81dae | ||
|
|
4887f355c8 | ||
|
|
9b32e6e3f2 | ||
|
|
74790ec80e | ||
|
|
afd8a754b4 | ||
|
|
bc1f7b3baa | ||
|
|
51690e68dc | ||
|
|
0fa076cb73 | ||
|
|
6506993704 | ||
|
|
a69db2ebfb | ||
|
|
d72ca7cb24 | ||
|
|
52dfa7b8f7 | ||
|
|
dae03c5730 | ||
|
|
14efe5d3fd | ||
|
|
653244d737 | ||
|
|
086831b3f5 | ||
|
|
6eca585fc1 | ||
|
|
f6ba36fc28 | ||
|
|
7406288d79 | ||
|
|
2e7fcbe5b8 | ||
|
|
3e7b9f8c14 | ||
|
|
e9189ccc06 | ||
|
|
8c00d715c2 | ||
|
|
d47a369cdf | ||
|
|
52bf6019fd | ||
|
|
25e61fd8ef | ||
|
|
3038be83a0 | ||
|
|
6e2447c70a | ||
|
|
486aba84ca | ||
|
|
b5944181e1 | ||
|
|
3f69cf506a | ||
|
|
6013897c50 | ||
|
|
27cf2a2532 | ||
|
|
d096cbddeb | ||
|
|
117503f214 | ||
|
|
5cc5fae750 | ||
|
|
d93032e1a9 | ||
|
|
507557b2cb | ||
|
|
ee5c3c185a | ||
|
|
e5592a2eb2 | ||
|
|
59200a6e73 | ||
|
|
f468f60ae1 | ||
|
|
9f60d827b6 | ||
|
|
5c936ddb65 | ||
|
|
d0bde62594 | ||
|
|
ada9192337 | ||
|
|
6161f588a9 |
@@ -98,7 +98,27 @@
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "VladislavParhomchik",
|
||||
"name": "Vladislav Parhomchik",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/83717836?v=4",
|
||||
"profile": "https://github.com/VladislavParhomchik",
|
||||
"contributions": [
|
||||
"test",
|
||||
"review"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "vznesh",
|
||||
"name": "Vignesh T.",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/28916792?v=4",
|
||||
"profile": "https://github.com/vznesh",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7
|
||||
"contributorsPerLine": 7,
|
||||
"skipCi": true
|
||||
}
|
||||
|
||||
4
.github/workflows/run-tests.yml
vendored
4
.github/workflows/run-tests.yml
vendored
@@ -12,12 +12,12 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
node-version: [lts/fermium]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
|
||||
19
.gitpod.yml
19
.gitpod.yml
@@ -1,8 +1,25 @@
|
||||
tasks:
|
||||
- init: nvm install --latest-npm && npm i -g @sasjs/cli
|
||||
- init: nvm install --lts && npm i -g @sasjs/cli
|
||||
|
||||
image:
|
||||
file: .gitpod.dockerfile
|
||||
vscode:
|
||||
extensions:
|
||||
- sasjs.sasjs-for-vscode
|
||||
|
||||
github:
|
||||
prebuilds:
|
||||
# enable for the master/default branch (defaults to true)
|
||||
master: true
|
||||
# enable for all branches in this repo (defaults to false)
|
||||
branches: false
|
||||
# enable for pull requests coming from this repo (defaults to true)
|
||||
pullRequests: true
|
||||
# enable for pull requests coming from forks (defaults to false)
|
||||
pullRequestsFromForks: true
|
||||
# add a "Review in Gitpod" button as a comment to pull requests (defaults to true)
|
||||
addComment: true
|
||||
# add a "Review in Gitpod" button to pull requests (defaults to false)
|
||||
addBadge: false
|
||||
# add a label once the prebuild is ready to pull requests (defaults to false)
|
||||
addLabel: prebuilt-in-gitpod
|
||||
@@ -9,3 +9,4 @@ sasjs/
|
||||
main.dox
|
||||
make_singlefile.sh
|
||||
*.md
|
||||
.all-contributorsrc
|
||||
|
||||
@@ -189,7 +189,7 @@ If you find this library useful, please leave a [star](https://github.com/sasjs/
|
||||
|
||||
## Contributors ✨
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
||||
@@ -208,6 +208,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/kkchandok"><img src="https://avatars.githubusercontent.com/u/46090627?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kkchandok</b></sub></a><br /><a href="#ideas-kkchandok" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vladislav Parhomchik</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
|
||||
<td align="center"><a href="https://github.com/vznesh"><img src="https://avatars.githubusercontent.com/u/28916792?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vignesh T.</b></sub></a><br /><a href="https://github.com/sasjs/core/issues?q=author%3Avznesh" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -36,13 +36,25 @@
|
||||
|
||||
%macro mf_getapploc(pgm);
|
||||
%if "&pgm"="" %then %do;
|
||||
%put &sysmacroname: No value provided;
|
||||
%return;
|
||||
%if %symexist(_program) %then %let pgm=&_program;
|
||||
%else %do;
|
||||
%put &sysmacroname: No value provided and no _program variable available;
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%local root;
|
||||
|
||||
/**
|
||||
* move up two levels to avoid matches on subfolder or service name
|
||||
* 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);
|
||||
|
||||
@@ -1,37 +1,58 @@
|
||||
/**
|
||||
@file
|
||||
@brief Assigns and returns an unused fileref
|
||||
@details
|
||||
@details Using the native approach for assigning filerefs fails as some
|
||||
procedures (such as proc http) do not recognise the temporary names (starting
|
||||
with a hash), returning a message such as:
|
||||
|
||||
> ERROR 22-322: Expecting a name.
|
||||
|
||||
This macro works by attempting a random fileref (with a prefix), seeing if it
|
||||
is already assigned, and if not - returning the fileref.
|
||||
|
||||
If your process can accept filerefs with the hash (#) prefix, then set
|
||||
`prefix=0` to revert to the native approach - which is significantly faster
|
||||
when there are a lot of filerefs in a session.
|
||||
|
||||
Use as follows:
|
||||
|
||||
%let fileref1=%mf_getuniquefileref();
|
||||
%let fileref2=%mf_getuniquefileref();
|
||||
%let fileref2=%mf_getuniquefileref(prefix=0);
|
||||
%put &fileref1 &fileref2;
|
||||
|
||||
which returns:
|
||||
which returns filerefs similar to:
|
||||
|
||||
> mcref0 mcref1
|
||||
> _7432233 #LN00070
|
||||
|
||||
@param prefix= first part of fileref. Remember that filerefs can only be 8
|
||||
characters, so a 7 letter prefix would mean that `maxtries` should be 10.
|
||||
@param maxtries= the last part of the libref. Provide an integer value.
|
||||
@param [in] prefix= (_) first part of fileref. Remember that filerefs can only
|
||||
be 8 characters, so a 7 letter prefix would mean `maxtries` should be 10.
|
||||
if using zero (0) as the prefix, a native assignment is used.
|
||||
@param [in] maxtries= (1000) the last part of the libref. Must be an integer.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_getuniquefileref(prefix=mcref,maxtries=1000);
|
||||
%local x fname;
|
||||
%let x=0;
|
||||
%do x=0 %to &maxtries;
|
||||
%if %sysfunc(fileref(&prefix&x)) > 0 %then %do;
|
||||
%let fname=&prefix&x;
|
||||
%macro mf_getuniquefileref(prefix=_,maxtries=1000);
|
||||
%local rc fname;
|
||||
%if &prefix=0 %then %do;
|
||||
%let rc=%sysfunc(filename(fname,,temp));
|
||||
%if &rc %then %put %sysfunc(sysmsg());
|
||||
&prefix&x
|
||||
%*put &sysmacroname: Fileref &prefix&x was assigned and returned;
|
||||
%return;
|
||||
&fname
|
||||
%end;
|
||||
%else %do;
|
||||
%local x len;
|
||||
%let len=%eval(8-%length(&prefix));
|
||||
%let x=0;
|
||||
%do x=0 %to &maxtries;
|
||||
%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);
|
||||
%if %sysfunc(fileref(&fname)) > 0 %then %do;
|
||||
%let rc=%sysfunc(filename(fname,,temp));
|
||||
%if &rc %then %put %sysfunc(sysmsg());
|
||||
&fname
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%put unable to find available fileref after &maxtries attempts;
|
||||
%end;
|
||||
%put unable to find available fileref in range &prefix.0-&maxtries;
|
||||
%mend mf_getuniquefileref;
|
||||
@@ -51,7 +51,8 @@
|
||||
%end;
|
||||
%end;
|
||||
%else %do;
|
||||
%put dataset &libds not opened! (rc=&dsid);
|
||||
%put &sysmacroname: dataset &libds not opened! (rc=&dsid);
|
||||
%put &sysmacroname: %sysfunc(sysmsg());
|
||||
%return;
|
||||
%end;
|
||||
|
||||
|
||||
@@ -43,7 +43,11 @@
|
||||
%let vlen = %str( );
|
||||
%end;
|
||||
%end;
|
||||
%else %put dataset &libds not opened! (rc=&dsid);
|
||||
%else %do;
|
||||
%put &sysmacroname: dataset &libds not opened! (rc=&dsid);
|
||||
%put &sysmacroname: %sysfunc(sysmsg());
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* Close dataset */
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
|
||||
@@ -43,7 +43,11 @@ returns:
|
||||
%let vnum = %str( );
|
||||
%end;
|
||||
%end;
|
||||
%else %put dataset &ds not opened! (rc=&dsid);
|
||||
%else %do;
|
||||
%put &sysmacroname: dataset &libds not opened! (rc=&dsid);
|
||||
%put &sysmacroname: %sysfunc(sysmsg());
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* Close dataset */
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
|
||||
@@ -39,7 +39,11 @@ Usage:
|
||||
%let vtype = %str( );
|
||||
%end;
|
||||
%end;
|
||||
%else %put dataset &libds not opened! (rc=&dsid);
|
||||
%else %do;
|
||||
%put &sysmacroname: dataset &libds not opened! (rc=&dsid);
|
||||
%put &sysmacroname: %sysfunc(sysmsg());
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* Close dataset */
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
|
||||
@@ -15,24 +15,47 @@
|
||||
recognise this and fetch the log of the parent session instead)
|
||||
@li STP environments must finish cleanly to avoid the log being sent to
|
||||
_webout. To assist with this, we also run stpsrvset('program error', 0)
|
||||
and set SYSCC=0. For 9.4m3 we take a unique approach - we open a macro
|
||||
but don't close it! This provides a graceful abort, EXCEPT when called
|
||||
called within a %include within a macro (and that macro contains additional
|
||||
logic). See mp_abort.test.nofix.sas for the example case.
|
||||
If you know of another way to gracefully abort a 9.4m3 STP session, we'd
|
||||
love to hear about it!
|
||||
and set SYSCC=0. We take a unique "soft abort" approach - we open a macro
|
||||
but don't close it! This works everywhere EXCEPT inside a \%include inside
|
||||
a macro. For that, we recommend you use mp_include.sas to perform the
|
||||
include, and then call \%mp_abort(mode=INCLUDE) from the source program (ie,
|
||||
OUTSIDE of the top-parent macro).
|
||||
|
||||
|
||||
@param mac= to contain the name of the calling macro
|
||||
@param msg= message to be returned
|
||||
@param iftrue= supply a condition under which the macro should be executed.
|
||||
@param errds= (work.mp_abort_errds) There is no clean way to end a process
|
||||
within a %include called within a macro. Furthermore, there is no way to
|
||||
test if a macro is called within a %include. To handle this particular
|
||||
scenario, the %include should be switched for the mp_include.sas macro.
|
||||
This provides an indicator that we are running a macro within a \%include
|
||||
(`_SYSINCLUDEFILEDEVICE`) and allows us to provide a dataset with the abort
|
||||
values (msg, mac).
|
||||
We can then run an abort cancel FILE to stop the include running, and pass
|
||||
the dataset back to the calling program to run a regular \%mp_abort().
|
||||
The dataset will contain the following fields:
|
||||
@li iftrue (1=1)
|
||||
@li msg (the message)
|
||||
@li mac (the mac param)
|
||||
|
||||
@version 9.4M3
|
||||
@param mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked for
|
||||
an abort status.
|
||||
Valid values:
|
||||
@li REGULAR (default)
|
||||
@li INCLUDE
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_include.sas
|
||||
|
||||
@version 9.4
|
||||
@author Allan Bowe
|
||||
@cond
|
||||
**/
|
||||
|
||||
%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)
|
||||
, errds=work.mp_abort_errds
|
||||
, mode=REGULAR
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%global sysprocessmode sysprocessname;
|
||||
@@ -43,9 +66,44 @@
|
||||
%if %length(&mac)>0 %then %put NOTE- called by &mac;
|
||||
%put NOTE - &msg;
|
||||
|
||||
%if %symexist(_SYSINCLUDEFILEDEVICE) %then %do;
|
||||
%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
|
||||
data &errds;
|
||||
iftrue='1=1';
|
||||
length mac $100 msg $5000;
|
||||
mac=symget('mac');
|
||||
msg=symget('msg');
|
||||
run;
|
||||
data _null_;
|
||||
abort cancel FILE;
|
||||
run;
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
/* Stored Process Server web app context */
|
||||
%if %symexist(_metaperson) or "&SYSPROCESSNAME "="Compute Server " %then %do;
|
||||
%if %symexist(_metaperson)
|
||||
or "&SYSPROCESSNAME "="Compute Server "
|
||||
or &mode=INCLUDE
|
||||
%then %do;
|
||||
options obs=max replace nosyntaxcheck mprint;
|
||||
%if &mode=INCLUDE %then %do;
|
||||
%if %sysfunc(exist(&errds))=1 %then %do;
|
||||
data _null_;
|
||||
set &errds;
|
||||
call symputx('iftrue',iftrue,'l');
|
||||
call symputx('mac',mac,'l');
|
||||
call symputx('msg',msg,'l');
|
||||
putlog (_all_)(=);
|
||||
run;
|
||||
%if (&iftrue)=0 %then %return;
|
||||
%end;
|
||||
%else %do;
|
||||
%put &sysmacroname: No include errors found;
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
/* extract log errs / warns, if exist */
|
||||
%local logloc logline;
|
||||
%global logmsg; /* capture global messages */
|
||||
@@ -100,7 +158,7 @@
|
||||
/* send response in SASjs JSON format */
|
||||
data _null_;
|
||||
file _webout mod lrecl=32000 encoding='utf-8';
|
||||
length msg $32767 debug $8;
|
||||
length msg $32767 ;
|
||||
sasdatetime=datetime();
|
||||
msg=cats(symget('msg'),'\n\nLog Extract:\n',symget('logmsg'));
|
||||
/* escape the quotes */
|
||||
@@ -111,7 +169,7 @@
|
||||
msg=cats('"',msg,'"');
|
||||
if symexist('_debug') then debug=quote(trim(symget('_debug')));
|
||||
else debug='""';
|
||||
if debug ge '"131"' then put '>>weboutBEGIN<<';
|
||||
put '>>weboutBEGIN<<';
|
||||
put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"';
|
||||
put ',"sasjsAbort" : [{';
|
||||
put ' "MSG":' msg ;
|
||||
@@ -131,29 +189,45 @@
|
||||
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
||||
put ',"_PROGRAM" : ' _PROGRAM ;
|
||||
put ",""SYSCC"" : ""&syscc"" ";
|
||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
||||
syserrortext=quote(trim(symget('syserrortext')));
|
||||
put ",""SYSERRORTEXT"" : " syserrortext;
|
||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||
put ",""SYSSITE"" : ""&syssite"" ";
|
||||
sysvlong=quote(trim(symget('sysvlong')));
|
||||
put ',"SYSVLONG" : ' sysvlong;
|
||||
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
|
||||
syswarningtext=quote(trim(symget('syswarningtext')));
|
||||
put ",""SYSWARNINGTEXT"" : " syswarningtext;
|
||||
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" ';
|
||||
put "}" @;
|
||||
if debug ge '"131"' then put '>>weboutEND<<';
|
||||
put '>>weboutEND<<';
|
||||
run;
|
||||
|
||||
%put _all_;
|
||||
|
||||
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
|
||||
data _null_;
|
||||
putlog 'stpsrvset program error and syscc';
|
||||
putlog 'stpsrvset program err and syscc';
|
||||
rc=stpsrvset('program error', 0);
|
||||
call symputx("syscc",0,"g");
|
||||
run;
|
||||
%if "%substr(&sysvlong.xxxxxxxxx,1,9)" ne "9.04.01M3" %then %do;
|
||||
%put NOTE: Ending SAS session due to:;
|
||||
%put NOTE- &msg;
|
||||
endsas;
|
||||
%end;
|
||||
/**
|
||||
* endsas kills 9.4m3 deployments by orphaning multibridges.
|
||||
* Abort variants are ungraceful (non zero return code)
|
||||
* This approach lets SAS run silently until the end :-)
|
||||
* Caution - fails when called within a %include within a macro
|
||||
* Use mp_include() to handle this.
|
||||
*/
|
||||
filename skip temp;
|
||||
data _null_;
|
||||
file skip;
|
||||
put '%macro skip();';
|
||||
comment '%mend skip; -> fix lint ';
|
||||
put '%macro skippy();';
|
||||
comment '%mend skippy; -> fix lint ';
|
||||
run;
|
||||
%inc skip;
|
||||
%end;
|
||||
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
|
||||
/* endsas kills the session making it harder to fetch results */
|
||||
@@ -169,24 +243,6 @@
|
||||
abort cancel nolist;
|
||||
run;
|
||||
%end;
|
||||
%else %if "%substr(&sysvlong.xxxxxxxxx,1,9)" = "9.04.01M3" %then %do;
|
||||
/**
|
||||
* endsas kills 9.4m3 deployments by orphaning multibridges.
|
||||
* Abort variants are ungraceful (non zero return code)
|
||||
* This approach lets SAS run silently until the end :-)
|
||||
* Caution - fails when called within a %include within a macro
|
||||
* See tests/mp_abort.test.1 for an example case.
|
||||
*/
|
||||
filename skip temp;
|
||||
data _null_;
|
||||
file skip;
|
||||
put '%macro skip();';
|
||||
comment '%mend skip; -> fix lint ';
|
||||
put '%macro skippy();';
|
||||
comment '%mend skippy; -> fix lint ';
|
||||
run;
|
||||
%inc skip;
|
||||
%end;
|
||||
%else %do;
|
||||
%abort cancel;
|
||||
%end;
|
||||
|
||||
57
base/mp_appendfile.sas
Normal file
57
base/mp_appendfile.sas
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
@file
|
||||
@brief Append (concatenate) two or more files.
|
||||
@details Will append one more more `appendrefs` (filerefs) to a `baseref`.
|
||||
Uses a binary mechanism, so will work with any file type. For that reason -
|
||||
use with care! And supply your own trailing carriage returns in each file..
|
||||
|
||||
Usage:
|
||||
|
||||
filename tmp1 temp;
|
||||
filename tmp2 temp;
|
||||
filename tmp3 temp;
|
||||
data _null_; file tmp1; put 'base file';
|
||||
data _null_; file tmp2; put 'append1';
|
||||
data _null_; file tmp3; put 'append2';
|
||||
run;
|
||||
%mp_appendfile(baseref=tmp1, appendrefs=tmp2 tmp3)
|
||||
|
||||
|
||||
@param [in] baseref= Fileref of the base file (should exist)
|
||||
@param [in] appendrefs= One or more filerefs to be appended to the base
|
||||
fileref. Space separated.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe, source: https://github.com/sasjs/core
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_abort.sas
|
||||
@li mp_binarycopy.sas
|
||||
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_appendfile(
|
||||
baseref=0,
|
||||
appendrefs=0
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%mp_abort(iftrue= (&baseref=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Baseref NOT specified!)
|
||||
)
|
||||
%mp_abort(iftrue= (&appendrefs=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Appendrefs NOT specified!)
|
||||
)
|
||||
|
||||
%local i;
|
||||
%do i=1 %to %sysfunc(countw(&appendrefs));
|
||||
%mp_abort(iftrue= (&syscc>0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(syscc=&syscc)
|
||||
)
|
||||
%mp_binarycopy(inref=%scan(&appendrefs,&i), outref=&baseref, mode=APPEND)
|
||||
%end;
|
||||
|
||||
%mend mp_appendfile;
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_existds.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_nobs.sas
|
||||
@li mp_abort.sas
|
||||
|
||||
@@ -115,6 +116,26 @@
|
||||
select count(*) into: orig from &lib..&ds;
|
||||
quit;
|
||||
|
||||
%local notfound tmp1 tmp2;
|
||||
%let tmp1=%mf_getuniquename();
|
||||
%let tmp2=%mf_getuniquename();
|
||||
|
||||
/* this is a bit convoluted - but using sql outobs=10 throws warnings */
|
||||
proc sql noprint;
|
||||
create view &tmp1 as
|
||||
select distinct &col
|
||||
from &lib..&ds
|
||||
where &col not in (
|
||||
select &ccol from &clib..&cds
|
||||
);
|
||||
data &tmp2;
|
||||
set &tmp1;
|
||||
if _n_>10 then stop;
|
||||
run;
|
||||
proc sql;
|
||||
select distinct &col into: notfound separated by ' ' from &tmp2;
|
||||
|
||||
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(syscc=&syscc after macro query)
|
||||
@@ -125,7 +146,7 @@
|
||||
test_description=symget('desc');
|
||||
test_result='FAIL';
|
||||
test_comments="&sysmacroname: &lib..&ds..&col has &result values "
|
||||
!!"not in &clib..&cds..&ccol ";
|
||||
!!"not in &clib..&cds..&ccol.. First 10 vals:"!!symget('notfound');
|
||||
%if &test=ANYVAL %then %do;
|
||||
if &result < &orig then test_result='PASS';
|
||||
%end;
|
||||
|
||||
@@ -9,10 +9,29 @@
|
||||
|
||||
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
|
||||
|
||||
@param inloc full, quoted "path/and/filename.ext" of the object to be copied
|
||||
@param outloc full, quoted "path/and/filename.ext" of object to be created
|
||||
@param inref can override default input fileref to avoid naming clash
|
||||
@param outref an override default output fileref to avoid naming clash
|
||||
To append to a file, use the mode option, eg:
|
||||
|
||||
filename tmp1 temp;
|
||||
filename tmp2 temp;
|
||||
data _null_;
|
||||
file tmp1;
|
||||
put 'stacking';
|
||||
run;
|
||||
|
||||
%mp_binarycopy(inref=tmp1, outref=tmp2, mode=APPEND)
|
||||
%mp_binarycopy(inref=tmp1, outref=tmp2, mode=APPEND)
|
||||
|
||||
|
||||
@param [in] inloc quoted "path/and/filename.ext" of the file to be copied
|
||||
@param [out] outloc quoted "path/and/filename.ext" of the file to be created
|
||||
@param [in] inref (____in) If provided, this fileref will take precedence over
|
||||
the `inloc` parameter
|
||||
@param [out] outref (____in) If provided, this fileref will take precedence
|
||||
over the `outloc` parameter. It must already exist!
|
||||
@param [in] mode (CREATE) Valid values:
|
||||
@li CREATE - Create the file (even if it already exists)
|
||||
@li APPEND - Append to the file (don't overwrite)
|
||||
|
||||
@returns nothing
|
||||
|
||||
@version 9.2
|
||||
@@ -24,20 +43,29 @@
|
||||
,outloc= /* full path and filename of object to be created */
|
||||
,inref=____in /* override default to use own filerefs */
|
||||
,outref=____out /* override default to use own filerefs */
|
||||
,mode=CREATE
|
||||
)/*/STORE SOURCE*/;
|
||||
%local mod outmode;
|
||||
%if &mode=APPEND %then %do;
|
||||
%let mod=mod;
|
||||
%let outmode='a';
|
||||
%end;
|
||||
%else %do;
|
||||
%let outmode='o';
|
||||
%end;
|
||||
/* these IN and OUT filerefs can point to anything */
|
||||
%if &inref = ____in %then %do;
|
||||
filename &inref &inloc lrecl=1048576 ;
|
||||
%end;
|
||||
%if &outref=____out %then %do;
|
||||
filename &outref &outloc lrecl=1048576 ;
|
||||
filename &outref &outloc lrecl=1048576 &mod;
|
||||
%end;
|
||||
|
||||
/* copy the file byte-for-byte */
|
||||
data _null_;
|
||||
length filein 8 fileid 8;
|
||||
filein = fopen("&inref",'I',1,'B');
|
||||
fileid = fopen("&outref",'O',1,'B');
|
||||
fileid = fopen("&outref",&outmode,1,'B');
|
||||
rec = '20'x;
|
||||
do while(fread(filein)=0);
|
||||
rc = fget(filein,rec,1);
|
||||
|
||||
78
base/mp_copyfolder.sas
Normal file
78
base/mp_copyfolder.sas
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
@file
|
||||
@brief A macro to recursively copy a directory
|
||||
@details Performs a recursive directory listing then works from top to bottom
|
||||
copying files and creating subdirectories.
|
||||
|
||||
Usage:
|
||||
|
||||
%let rootdir=%sysfunc(pathname(work))/demo;
|
||||
%let copydir=%sysfunc(pathname(work))/demo_copy;
|
||||
%mf_mkdir(&rootdir)
|
||||
%mf_mkdir(&rootdir/subdir)
|
||||
%mf_mkdir(&rootdir/subdir/subsubdir)
|
||||
data "&rootdir/subdir/example.sas7bdat";
|
||||
run;
|
||||
|
||||
%mp_copyfolder(&rootdir,©dir)
|
||||
|
||||
@param source Unquoted path to the folder to copy from.
|
||||
@param target Unquoted path to the folder to copy to.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_isdir.sas
|
||||
@li mf_mkdir.sas
|
||||
@li mp_abort.sas
|
||||
@li mp_dirlist.sas
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_copyfolder.test.sas
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_copyfolder(source,target);
|
||||
|
||||
%mp_abort(iftrue=(%mf_isdir(&source)=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Source dir does not exist (&source))
|
||||
)
|
||||
|
||||
%mf_mkdir(&target)
|
||||
|
||||
%mp_abort(iftrue=(%mf_isdir(&target)=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Target dir could not be created (&target))
|
||||
)
|
||||
|
||||
/* prep temp table */
|
||||
%local tempds;
|
||||
%let tempds=%mf_getuniquename();
|
||||
|
||||
/* recursive directory listing */
|
||||
%mp_dirlist(path=&source,outds=work.&tempds, maxdepth=MAX)
|
||||
|
||||
/* create folders and copy content */
|
||||
data _null_;
|
||||
set work.&tempds;
|
||||
filepath2="&target/"!!substr(filepath,%length(&source)+2);
|
||||
if file_or_folder='folder' then call execute('%mf_mkdir('!!filepath2!!')');
|
||||
else do;
|
||||
length fref1 fref2 $8;
|
||||
rc1=filename(fref1,filepath,'disk','recfm=n');
|
||||
rc2=filename(fref2,filepath2,'disk','recfm=n');
|
||||
if fcopy(fref1,fref2) ne 0 then do;
|
||||
sysmsg=sysmsg();
|
||||
putlog "%str(ERR)OR: Unable to copy " filepath " to " filepath2;
|
||||
putlog sysmg=;
|
||||
end;
|
||||
end;
|
||||
rc=filename(fref1);
|
||||
rc=filename(fref2);
|
||||
run;
|
||||
|
||||
/* tidy up */
|
||||
proc sql;
|
||||
drop table work.&tempds;
|
||||
|
||||
%mend mp_copyfolder;
|
||||
69
base/mp_deletefolder.sas
Normal file
69
base/mp_deletefolder.sas
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
@file
|
||||
@brief A macro to delete a directory
|
||||
@details Will delete all folder content (including subfolder content) and
|
||||
finally, the folder itself.
|
||||
|
||||
Usage:
|
||||
|
||||
%let rootdir=%sysfunc(pathname(work))/demo;
|
||||
%mf_mkdir(&rootdir)
|
||||
%mf_mkdir(&rootdir/subdir)
|
||||
%mf_mkdir(&rootdir/subdir/subsubdir)
|
||||
data "&rootdir/subdir/example.sas7bdat";
|
||||
run;
|
||||
|
||||
%mp_deletefolder(&rootdir)
|
||||
|
||||
@param path Unquoted path to the folder to delete.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_isdir.sas
|
||||
@li mp_dirlist.sas
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_deletefolder.test.sas
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_deletefolder(folder);
|
||||
/* proceed if valid directory */
|
||||
%if %mf_isdir(&folder)=1 %then %do;
|
||||
|
||||
/* prep temp table */
|
||||
%local tempds;
|
||||
%let tempds=%mf_getuniquename();
|
||||
|
||||
/* recursive directory listing */
|
||||
%mp_dirlist(path=&folder,outds=work.&tempds, maxdepth=MAX)
|
||||
|
||||
/* sort descending level so can delete folder contents before folders */
|
||||
proc sort data=work.&tempds;
|
||||
by descending level;
|
||||
run;
|
||||
|
||||
/* ensure top level folder is removed at the end */
|
||||
proc sql;
|
||||
insert into work.&tempds set filepath="&folder";
|
||||
|
||||
/* delete everything */
|
||||
data _null_;
|
||||
set work.&tempds end=last;
|
||||
length fref $8;
|
||||
rc=filename(fref,filepath);
|
||||
rc=fdelete(fref);
|
||||
if rc then do;
|
||||
msg=sysmsg();
|
||||
put "&sysmacroname:" / rc= / msg= / filepath=;
|
||||
end;
|
||||
rc=filename(fref);
|
||||
run;
|
||||
|
||||
/* tidy up */
|
||||
proc sql;
|
||||
drop table work.&tempds;
|
||||
|
||||
%end;
|
||||
%else %put &sysmacroname: &folder: is not a valid / accessible folder. ;
|
||||
%mend mp_deletefolder;
|
||||
@@ -3,20 +3,13 @@
|
||||
@brief Returns all files and subdirectories within a specified parent
|
||||
@details When used with getattrs=NO, is not OS specific (uses dopen / dread).
|
||||
|
||||
If getattrs=YES then the doptname / foptname functions are used to scan all
|
||||
properties - any characters that are not valid in a SAS name (v7) are simply
|
||||
stripped, and the table is transposed so theat each property is a column
|
||||
and there is one file per row. An attempt is made to get all properties
|
||||
whether a file or folder, but some files/folders cannot be accessed, and so
|
||||
not all properties can / will be populated.
|
||||
|
||||
Credit for the rename approach:
|
||||
https://communities.sas.com/t5/SAS-Programming/SAS-Function-to-convert-string-to-Legal-SAS-Name/m-p/27375/highlight/true#M5003
|
||||
|
||||
|
||||
usage:
|
||||
|
||||
%mp_dirlist(path=/some/location,outds=myTable)
|
||||
%mp_dirlist(path=/some/location, outds=myTable, maxdepth=MAX)
|
||||
|
||||
%mp_dirlist(outds=cwdfileprops, getattrs=YES)
|
||||
|
||||
@@ -30,11 +23,19 @@
|
||||
X CMD) do please raise an issue!
|
||||
|
||||
|
||||
@param path= for which to return contents
|
||||
@param fref= Provide a DISK engine fileref as an alternative to PATH
|
||||
@param outds= the output dataset to create
|
||||
@param getattrs= YES/NO (default=NO). Uses doptname and foptname to return
|
||||
all attributes for each file / folder.
|
||||
@param [in] path= for which to return contents
|
||||
@param [in] fref= Provide a DISK engine fileref as an alternative to PATH
|
||||
@param [in] maxdepth= (0) Set to a positive integer to indicate the level of
|
||||
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
|
||||
recursion, set to MAX.
|
||||
@param [out] outds= the output dataset to create
|
||||
@param [out] getattrs= (NO) If getattrs=YES then the doptname / foptname
|
||||
functions are used to scan all properties - any characters that are not
|
||||
valid in a SAS name (v7) are simply stripped, and the table is transposed
|
||||
so theat each property is a column and there is one file per row. An
|
||||
attempt is made to get all properties whether a file or folder, but some
|
||||
files/folders cannot be accessed, and so not all properties can / will be
|
||||
populated.
|
||||
|
||||
|
||||
@returns outds contains the following variables:
|
||||
@@ -44,8 +45,15 @@
|
||||
- filename (just the file name)
|
||||
- ext (.extension)
|
||||
- msg (system message if any issues)
|
||||
- level (depth of folder)
|
||||
- OS SPECIFIC variables, if <code>getattrs=</code> is used.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_dropmembers.sas
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_dirlist.test.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
@@ -54,14 +62,27 @@
|
||||
, fref=0
|
||||
, outds=work.mp_dirlist
|
||||
, getattrs=NO
|
||||
, maxdepth=0
|
||||
, level=0 /* The level of recursion to perform. For internal use only. */
|
||||
)/*/STORE SOURCE*/;
|
||||
%let getattrs=%upcase(&getattrs)XX;
|
||||
|
||||
data &outds(compress=no
|
||||
keep=file_or_folder filepath filename ext msg directory
|
||||
/* temp table */
|
||||
%local out_ds;
|
||||
data;run;
|
||||
%let out_ds=%str(&syslast);
|
||||
|
||||
/* drop main (top) table if it exists */
|
||||
%if &level=0 %then %do;
|
||||
%mp_dropmembers(%scan(&outds,-1,.), libref=WORK)
|
||||
%end;
|
||||
|
||||
data &out_ds(compress=no
|
||||
keep=file_or_folder filepath filename ext msg directory level
|
||||
);
|
||||
length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80
|
||||
ext $20 msg $200;
|
||||
retain level &level;
|
||||
%if &fref=0 %then %do;
|
||||
rc = filename(fref, "&path");
|
||||
%end;
|
||||
@@ -119,8 +140,8 @@ data &outds(compress=no
|
||||
run;
|
||||
|
||||
%if %substr(&getattrs,1,1)=Y %then %do;
|
||||
data &outds;
|
||||
set &outds;
|
||||
data &out_ds;
|
||||
set &out_ds;
|
||||
length infoname infoval $60 fref $8;
|
||||
rc=filename(fref,filepath);
|
||||
drop rc infoname fid i close fref;
|
||||
@@ -161,10 +182,36 @@ run;
|
||||
run;
|
||||
proc sort;
|
||||
by filepath sasname;
|
||||
proc transpose data=&outds out=&outds(drop=_:);
|
||||
proc transpose data=&out_ds out=&out_ds(drop=_:);
|
||||
id sasname;
|
||||
var infoval;
|
||||
by filepath file_or_folder filename ext ;
|
||||
run;
|
||||
%end;
|
||||
|
||||
data &out_ds;
|
||||
set &out_ds(where=(filepath ne ''));
|
||||
run;
|
||||
|
||||
/* update main table */
|
||||
proc append base=&outds data=&out_ds;
|
||||
run;
|
||||
|
||||
/* recursive call */
|
||||
%if &maxdepth>&level or &maxdepth=MAX %then %do;
|
||||
data _null_;
|
||||
set &out_ds;
|
||||
where file_or_folder='folder';
|
||||
length code $10000;
|
||||
code=cats('%nrstr(%mp_dirlist(path=',filepath,",outds=&outds"
|
||||
,",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");
|
||||
put code=;
|
||||
call execute(code);
|
||||
run;
|
||||
%end;
|
||||
|
||||
/* tidy up */
|
||||
proc sql;
|
||||
drop table &out_ds;
|
||||
|
||||
%mend mp_dirlist;
|
||||
@@ -2,12 +2,21 @@
|
||||
@file
|
||||
@brief Create a CARDS file from a SAS dataset.
|
||||
@details Uses dataset attributes to convert all data into datalines.
|
||||
Running the generated file will rebuild the original dataset.
|
||||
Running the generated file will rebuild the original dataset. Includes
|
||||
support for large decimals, binary data, PROCESSED_DTTM columns, and
|
||||
alternative encoding. If the input dataset is empty, the cards file will
|
||||
still be created.
|
||||
|
||||
Additional support to generate a random sample and max rows.
|
||||
|
||||
Usage:
|
||||
|
||||
%mp_ds2cards(base_ds=sashelp.class
|
||||
, tgt_ds=work.class
|
||||
, cards_file= "C:\temp\class.sas"
|
||||
, maxobs=5)
|
||||
, showlog=NO
|
||||
, maxobs=5
|
||||
)
|
||||
|
||||
TODO:
|
||||
- labelling the dataset
|
||||
@@ -18,15 +27,24 @@
|
||||
that is converted to a cards file.
|
||||
@param [in] tgt_ds= Table that the generated cards file would create.
|
||||
Optional - if omitted, will be same as BASE_DS.
|
||||
@param [out] cards_file= Location in which to write the (.sas) cards file
|
||||
@param [in] maxobs= to limit output to the first <code>maxobs</code>
|
||||
observations
|
||||
@param [in] showlog= whether to show generated cards file in the SAS log
|
||||
(YES/NO)
|
||||
@param [in] outencoding= provide encoding value for file statement (eg utf-8)
|
||||
@param [in] append= If NO then will rebuild the cards file if it already
|
||||
@param [out] cards_file= ("%sysfunc(pathname(work))/cardgen.sas") Location in
|
||||
which to write the (.sas) cards file
|
||||
@param [in] maxobs= (max) To limit output to the first <code>maxobs</code>
|
||||
observations, enter an integer here.
|
||||
@param [in] random_sample= (NO) Set to YES to generate a random sample of
|
||||
data. Can be quite slow.
|
||||
@param [in] showlog= (YES) Whether to show generated cards file in the SAS
|
||||
log. Valid values:
|
||||
@li YES
|
||||
@li NO
|
||||
@param [in] outencoding= Provide encoding value for file statement (eg utf-8)
|
||||
@param [in] append= (NO) If NO then will rebuild the cards file if it already
|
||||
exists, otherwise will append to it. Used by the mp_lib2cards.sas macro.
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_lib2cards.sas
|
||||
@li mp_ds2inserts.sas
|
||||
@li mp_mdtablewrite.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -51,15 +69,15 @@
|
||||
%if (&tgt_ds = ) %then %let tgt_ds=&base_ds;
|
||||
%if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.);
|
||||
%if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding";
|
||||
%if ("&append" = "") %then %let append=;
|
||||
%if ("&append" = "" or "&append" = "NO") %then %let append=;
|
||||
%else %let append=mod;
|
||||
|
||||
/* get varcount */
|
||||
%let nvars=0;
|
||||
proc sql noprint;
|
||||
select count(*) into: nvars from dictionary.columns
|
||||
where libname="%scan(%upcase(&base_ds),1)"
|
||||
and memname="%scan(%upcase(&base_ds),2)";
|
||||
where upcase(libname)="%scan(%upcase(&base_ds),1)"
|
||||
and upcase(memname)="%scan(%upcase(&base_ds),2)";
|
||||
%if &nvars=0 %then %do;
|
||||
%put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.;
|
||||
%return;
|
||||
@@ -115,8 +133,8 @@ proc sql
|
||||
reset outobs=max;
|
||||
create table datalines1 as
|
||||
select name,type,length,varnum,format,label from dictionary.columns
|
||||
where libname="%upcase(%scan(&base_ds,1))"
|
||||
and memname="%upcase(%scan(&base_ds,2))";
|
||||
where upcase(libname)="%upcase(%scan(&base_ds,1))"
|
||||
and upcase(memname)="%upcase(%scan(&base_ds,2))";
|
||||
|
||||
/**
|
||||
Due to long decimals cannot use best. format
|
||||
@@ -137,7 +155,18 @@ data datalines_2;
|
||||
,put(',name,',best32.-l)
|
||||
,substrn(put(',name,',bestd32.-l),1
|
||||
,findc(put(',name,',bestd32.-l),"0","TBK")))');
|
||||
else dataline=name;
|
||||
/**
|
||||
* binary data must be converted, to store in text format. It is identified
|
||||
* by the presence of the $HEX keyword in the format.
|
||||
*/
|
||||
else if upcase(format)=:'$HEX' then
|
||||
dataline=cats('put(trim(',name,'),',format,')');
|
||||
/**
|
||||
* There is no easy way to store line breaks in a cards file.
|
||||
* To discuss this, use: https://github.com/sasjs/core/issues/80
|
||||
* Removing all nonprintables with kw (keep writeable)
|
||||
*/
|
||||
else dataline=cats('compress(',name,', ,"kw")');
|
||||
run;
|
||||
|
||||
proc sql noprint;
|
||||
@@ -162,7 +191,8 @@ data _null_;
|
||||
|
||||
|
||||
/* Build input statement */
|
||||
if type='char' then type3=':$char.';
|
||||
if upcase(format)=:'$HEX' then type3=':'!!format;
|
||||
else if type='char' then type3=':$char.';
|
||||
str2=put(name,$33.)||type3;
|
||||
|
||||
|
||||
@@ -184,11 +214,12 @@ data _null_;
|
||||
file &cards_file. &outencoding lrecl=32767 termstr=nl &append;
|
||||
length __attrib $32767;
|
||||
if _n_=1 then do;
|
||||
put '/*******************************************************************';
|
||||
put " Datalines for %upcase(%scan(&base_ds,2)) dataset ";
|
||||
put " Generated by %nrstr(%%)mp_ds2cards()";
|
||||
put " Available on github.com/sasjs/core";
|
||||
put '********************************************************************/';
|
||||
put '/**';
|
||||
put ' @file';
|
||||
put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset";
|
||||
put " @details Generated by %nrstr(%%)mp_ds2cards()";
|
||||
put " Available on github.com/sasjs/core";
|
||||
put '**/';
|
||||
put "data &tgt_ds &indexes;";
|
||||
put "attrib ";
|
||||
%do i = 1 %to &nvars;
|
||||
@@ -212,11 +243,11 @@ data _null_;
|
||||
put 'run;';
|
||||
end;
|
||||
else do;
|
||||
put "infile cards dsd delimiter=',';";
|
||||
put "infile cards dsd;";
|
||||
put "input ";
|
||||
%do i = 1 %to &nvars.;
|
||||
%if(%length(&&input_stmt_&i..)) %then
|
||||
put " &&input_stmt_&i..";
|
||||
put " &&input_stmt_&i..";
|
||||
;
|
||||
%end;
|
||||
put ";";
|
||||
|
||||
@@ -25,10 +25,13 @@
|
||||
options:
|
||||
@li SAS (default) - suitable for regular proc sql
|
||||
@li PGSQL - Used for Postgres databases
|
||||
@param [in] applydttm= (YES) If YES, any columns using datetime formats will
|
||||
be converted to native DB datetime literals
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_existfileref.sas
|
||||
@li mf_getvarcount.sas
|
||||
@li mf_getvarformat.sas
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_getvartype.sas
|
||||
|
||||
@@ -37,6 +40,7 @@
|
||||
**/
|
||||
|
||||
%macro mp_ds2inserts(ds, outref=0,schema=0,outds=0,flavour=SAS,maxobs=max
|
||||
,applydttm=YES
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if not %sysfunc(exist(&ds)) %then %do;
|
||||
@@ -114,10 +118,11 @@ data _null_;
|
||||
length _____str $32767;
|
||||
format _numeric_ best.;
|
||||
format _character_ ;
|
||||
%local i comma var vtype;
|
||||
%local i comma var vtype vfmt;
|
||||
%do i=1 %to %sysfunc(countw(&varlist));
|
||||
%let var=%scan(&varlist,&i);
|
||||
%let vtype=%mf_getvartype(&ds,&var);
|
||||
%let vfmt=%upcase(%mf_getvarformat(&ds,&var,force=1));
|
||||
%if &i=1 %then %do;
|
||||
%if &flavour=SAS %then %do;
|
||||
put "insert into &schema.&outds set ";
|
||||
@@ -147,7 +152,13 @@ data _null_;
|
||||
%end;
|
||||
%else %if &flavour=PGSQL %then %do;
|
||||
if missing(&var) then put 'NULL';
|
||||
else put &var;
|
||||
%if &applydttm=YES and "%substr(&vfmt.xxxxxxxx,1,8)"="DATETIME"
|
||||
%then %do;
|
||||
else put "TIMESTAMP '" &var E8601DT25.6 "'";
|
||||
%end;
|
||||
%else %do;
|
||||
else put &var;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%else %do;
|
||||
|
||||
@@ -78,7 +78,7 @@ run;
|
||||
|
||||
data &outds;
|
||||
if &sqlrc or &syscc or &syserr then do;
|
||||
REASON_CD='VALIDATION_ERROR: '!!
|
||||
REASON_CD='VALIDATION_ERR'!!'OR: '!!
|
||||
coalescec(symget('SYSERRORTEXT'),symget('SYSWARNINGTEXT'));
|
||||
output;
|
||||
end;
|
||||
|
||||
65
base/mp_getcols.sas
Normal file
65
base/mp_getcols.sas
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
@file
|
||||
@brief Creates a dataset with column metadata.
|
||||
@details This macro takes the `proc contents` output and "tidies it up" in the
|
||||
following ways:
|
||||
|
||||
@li Blank labels are filled in with column names
|
||||
@li Formats are reconstructed with default values
|
||||
@li Types such as DATE / TIME / DATETIME are inferred from the formats
|
||||
|
||||
Example usage:
|
||||
|
||||
%mp_getcols(sashelp.airline,outds=work.myds)
|
||||
|
||||
@param ds The dataset from which to obtain column metadata
|
||||
@param outds= (work.cols) The output dataset to create. Sample data:
|
||||
|NAME $|LENGTH 8|VARNUM 8|LABEL $|FORMAT $49|TYPE $1 |DDTYPE $|
|
||||
|---|---|---|---|---|---|---|
|
||||
|AIR|8|2|international airline travel (thousands)|8.|N|NUMERIC|
|
||||
|DATE|8|1|DATE|MONYY.|N|DATE|
|
||||
|REGION|3|3|REGION|$3.|C|CHARACTER|
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mf_getvarlist.sas
|
||||
@li mm_getcols.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_getcols(ds, outds=work.cols);
|
||||
|
||||
proc contents noprint data=&ds
|
||||
out=_data_ (keep=name type length label varnum format:);
|
||||
run;
|
||||
data &outds(keep=name type length varnum format label ddtype);
|
||||
set &syslast(rename=(format=format2 type=type2));
|
||||
name=upcase(name);
|
||||
if type2=2 then do;
|
||||
length format $49.;
|
||||
if format2='' then format=cats('$',length,'.');
|
||||
else if formatl=0 then format=cats(format2,'.');
|
||||
else format=cats(format2,formatl,'.');
|
||||
type='C';
|
||||
ddtype='CHARACTER';
|
||||
end;
|
||||
else do;
|
||||
if format2='' then format=cats(length,'.');
|
||||
else if formatl=0 then format=cats(format2,'.');
|
||||
else if formatd=0 then format=cats(format2,formatl,'.');
|
||||
else format=cats(format2,formatl,'.',formatd);
|
||||
type='N';
|
||||
if format=:'DATETIME' then ddtype='DATETIME';
|
||||
else if format=:'DATE' or format=:'DDMMYY' or format=:'MMDDYY'
|
||||
or format=:'YYMMDD' or format=:'E8601DA' or format=:'B8601DA'
|
||||
or format=:'MONYY'
|
||||
then ddtype='DATE';
|
||||
else if format=:'TIME' then ddtype='TIME';
|
||||
else ddtype='NUMERIC';
|
||||
end;
|
||||
if label='' then label=name;
|
||||
run;
|
||||
|
||||
%mend mp_getcols;
|
||||
@@ -39,21 +39,24 @@
|
||||
/* must use SQL as proc datasets does not support length changes */
|
||||
proc sql noprint;
|
||||
create table &outds as
|
||||
select a.TABLE_CATALOG as libref
|
||||
,a.TABLE_NAME
|
||||
select upcase(a.TABLE_CATALOG) as libref
|
||||
,upcase(a.TABLE_NAME) as TABLE_NAME
|
||||
,a.constraint_type
|
||||
,a.constraint_name
|
||||
,b.column_name
|
||||
from dictionary.TABLE_CONSTRAINTS a
|
||||
left join dictionary.constraint_column_usage b
|
||||
on a.TABLE_CATALOG=b.TABLE_CATALOG
|
||||
and a.TABLE_NAME=b.TABLE_NAME
|
||||
on upcase(a.TABLE_CATALOG)=upcase(b.TABLE_CATALOG)
|
||||
and upcase(a.TABLE_NAME)=upcase(b.TABLE_NAME)
|
||||
and a.constraint_name=b.constraint_name
|
||||
where a.TABLE_CATALOG="&lib"
|
||||
and b.TABLE_CATALOG="&lib"
|
||||
/**
|
||||
* We cannot apply this clause to the underlying dictionary table. See:
|
||||
* https://communities.sas.com/t5/SAS-Programming/Unexpected-Where-Clause-behaviour-in-dictionary-TABLE/m-p/771554#M244867
|
||||
*/
|
||||
where calculated libref="&lib"
|
||||
%if "&ds" ne "" %then %do;
|
||||
and a.TABLE_NAME="&ds"
|
||||
and b.TABLE_NAME="&ds"
|
||||
and upcase(a.TABLE_NAME)="&ds"
|
||||
and upcase(b.TABLE_NAME)="&ds"
|
||||
%end;
|
||||
;
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ run;
|
||||
proc sql noprint;
|
||||
select sysvalue into: schemaactual
|
||||
from dictionary.libnames
|
||||
where libname="&libref" and engine='SQLSVR';
|
||||
where upcase(libname)="&libref" and engine='SQLSVR';
|
||||
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
|
||||
|
||||
%do x=1 %to %sysfunc(countw(&dsnlist));
|
||||
@@ -304,7 +304,7 @@ run;
|
||||
proc sql noprint;
|
||||
select sysvalue into: schemaactual
|
||||
from dictionary.libnames
|
||||
where libname="&libref" and engine='POSTGRES';
|
||||
where upcase(libname)="&libref" and engine='POSTGRES';
|
||||
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
|
||||
53
base/mp_gsubfile.sas
Normal file
53
base/mp_gsubfile.sas
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
@file
|
||||
@brief Performs a text substitution on a file
|
||||
@details Makes use of the GSUB function in LUA to perform a text substitution
|
||||
in a file - either in-place, or writing to a new location. The benefit of
|
||||
using LUA is that the entire file can be loaded into a single variable,
|
||||
thereby side stepping the 32767 character limit in a data step.
|
||||
|
||||
Usage:
|
||||
|
||||
%let file=%sysfunc(pathname(work))/file.txt;
|
||||
%let str=replace/me;
|
||||
%let rep=with/this;
|
||||
data _null_;
|
||||
file "&file";
|
||||
put "&str";
|
||||
run;
|
||||
%mp_gsubfile(file=&file, patternvar=str, replacevar=rep)
|
||||
data _null_;
|
||||
infile "&file";
|
||||
input;
|
||||
list;
|
||||
run;
|
||||
|
||||
@param file= (0) The file to perform the substitution on
|
||||
@param patternvar= A macro variable containing the Lua
|
||||
[pattern](https://www.lua.org/pil/20.2.html) to search for. Due to the use
|
||||
of special (magic) characters in Lua patterns, it is safer to pass the NAME
|
||||
of the macro variable containing the string, rather than the value itself.
|
||||
@param replacevar= The name of the macro variable containing the replacement
|
||||
_string_.
|
||||
@param outfile= (0) The file to write the output to. If zero, then the file
|
||||
is overwritten in-place.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li ml_gsubfile.sas
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_gsubfile.test.sas
|
||||
|
||||
@version 9.4
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_gsubfile(file=0,
|
||||
patternvar=,
|
||||
replacevar=,
|
||||
outfile=0
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%ml_gsubfile()
|
||||
|
||||
%mend mp_gsubfile;
|
||||
104
base/mp_include.sas
Normal file
104
base/mp_include.sas
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
@file
|
||||
@brief Performs a wrapped \%include
|
||||
@details This macro wrapper is necessary if you need your included code to
|
||||
know that it is being \%included.
|
||||
|
||||
If you are using %include in a regular program, you could make use of the
|
||||
following macro variables:
|
||||
|
||||
@li SYSINCLUDEFILEDEVICE
|
||||
@li SYSINCLUDEFILEDIR
|
||||
@li SYSINCLUDEFILEFILEREF
|
||||
@li SYSINCLUDEFILENAME
|
||||
|
||||
However these variables are NOT available inside a macro, as documented here:
|
||||
https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/mcrolref/n1j5tcc0n2xczyn1kg1o0606gsv9.htm
|
||||
|
||||
This macro can be used in place of the %include statement, and will insert
|
||||
the following (equivalent) global variables:
|
||||
|
||||
@li _SYSINCLUDEFILEDEVICE
|
||||
@li _SYSINCLUDEFILEDIR
|
||||
@li _SYSINCLUDEFILEFILEREF
|
||||
@li _SYSINCLUDEFILENAME
|
||||
|
||||
These can be used whenever testing _within a macro_. Outside of the macro,
|
||||
the regular automatic variables will still be available (thanks to a
|
||||
concatenated file list in the include statement).
|
||||
|
||||
Example usage:
|
||||
|
||||
filename example temp;
|
||||
data _null_;
|
||||
file example;
|
||||
put '%macro test();';
|
||||
put '%put &=_SYSINCLUDEFILEFILEREF;';
|
||||
put '%put &=SYSINCLUDEFILEFILEREF;';
|
||||
put '%mend; %test()';
|
||||
put '%put &=SYSINCLUDEFILEFILEREF;';
|
||||
run;
|
||||
%mp_include(example)
|
||||
|
||||
@param [in] fileref The fileref of the file to be included. Must be provided.
|
||||
@param [in] prefix= (_) The prefix to apply to the global variables.
|
||||
@param [in] opts= (SOURCE2) The options to apply to the %inc statement
|
||||
@param [in] errds= (work.mp_abort_errds) There is no clean way to end a
|
||||
process within a %include called within a macro. Furthermore, there is no
|
||||
way to test if a macro is called within a %include. To handle this
|
||||
particular scenario, the %mp_abort() macro will test for the existence of
|
||||
the `_SYSINCLUDEFILEDEVICE` variable and return the outputs (msg,mac) inside
|
||||
this dataset.
|
||||
It will then run an abort cancel FILE to stop the include running, and pass
|
||||
the dataset back.
|
||||
NOTE - it is NOT possible to read this dataset as part of _this_ macro -
|
||||
when running abort cancel FILE, ALL macros are closed, so instead it is
|
||||
necessary to invoke "%mp_abort(mode=INCLUDE)" OUTSIDE of any macro wrappers.
|
||||
|
||||
|
||||
@version 9.4
|
||||
@author Allan Bowe
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquefileref.sas
|
||||
@li mp_abort.sas
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_include(fileref
|
||||
,prefix=_
|
||||
,opts=SOURCE2
|
||||
,errds=work.mp_abort_errds
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
/* prepare precode */
|
||||
%local tempref;
|
||||
%let tempref=%mf_getuniquefileref();
|
||||
data _null_;
|
||||
file &tempref;
|
||||
set sashelp.vextfl(where=(fileref="%upcase(&fileref)"));
|
||||
put '%let _SYSINCLUDEFILEDEVICE=' xengine ';';
|
||||
name=scan(xpath,-1,'/\');
|
||||
put '%let _SYSINCLUDEFILENAME=' name ';';
|
||||
path=subpad(xpath,1,length(xpath)-length(name)-1);
|
||||
put '%let _SYSINCLUDEFILEDIR=' path ';';
|
||||
put '%let _SYSINCLUDEFILEFILEREF=' "&fileref;";
|
||||
run;
|
||||
|
||||
/* prepare the errds */
|
||||
data &errds;
|
||||
length msg mac $1000;
|
||||
iftrue='1=0';
|
||||
run;
|
||||
|
||||
/* include the include */
|
||||
%inc &tempref &fileref/&opts;
|
||||
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=%str(&_SYSINCLUDEFILEDIR/&_SYSINCLUDEFILENAME)
|
||||
,msg=%str(syscc=&syscc after executing &_SYSINCLUDEFILENAME)
|
||||
)
|
||||
|
||||
filename &tempref clear;
|
||||
|
||||
%mend mp_include;
|
||||
@@ -4,7 +4,7 @@
|
||||
@details PROC JSON is faster but will produce errs like the ones below if
|
||||
special chars are encountered.
|
||||
|
||||
> ERROR: Some code points did not transcode.
|
||||
> (ERR)OR: Some code points did not transcode.
|
||||
|
||||
> An object or array close is not valid at this point in the JSON text.
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
@param [out] outref= Output fileref in which to create the insert statements.
|
||||
If it exists, it will be appended to, otherwise it will be created.
|
||||
@param [out] schema= (0) The schema of the target database, or the libref.
|
||||
@param [in] applydttm= (YES) If YES, any columns using datetime formats will
|
||||
be converted to native DB datetime literals
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -38,6 +40,7 @@
|
||||
,outref=0
|
||||
,schema=0
|
||||
,maxobs=max
|
||||
,applydttm=YES
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
/* Find the tables */
|
||||
@@ -67,6 +70,7 @@ select distinct lowcase(memname)
|
||||
,outds=&ds
|
||||
,flavour=&flavour
|
||||
,maxobs=&maxobs
|
||||
,applydttm=&applydttm
|
||||
)
|
||||
%end;
|
||||
|
||||
|
||||
255
base/mp_lockanytable.sas
Normal file
255
base/mp_lockanytable.sas
Normal file
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
@file
|
||||
@brief Mechanism for locking tables to prevent parallel modifications
|
||||
@details Uses a control table to enable ANY table to be locked for updates.
|
||||
Only useful if every update uses the macro! Used heavily within
|
||||
[Data Controller for SAS](https://datacontroller.io).
|
||||
|
||||
The underlying table is structured as per the MAKETABLE action.
|
||||
|
||||
@param [in] action The action to be performed. Valid values:
|
||||
@li LOCK - Sets the lock flag, also confirms if a SAS lock is available
|
||||
@li UNLOCK - Unlocks the table
|
||||
@li MAKETABLE - creates the control table (ctl_ds)
|
||||
@param [in] lib= (WORK) The libref of the table to lock. Should already be
|
||||
assigned.
|
||||
@param [in] ds= The dataset to lock
|
||||
@param [in] ref= A meaningful reference to enable the lock to be traced. Max
|
||||
length is 200 characters.
|
||||
@param [out] ctl_ds= (0) The control table which controls the actual locking.
|
||||
Should already be assigned and available.
|
||||
@param [in] loops= (25) Number of times to check for a lock.
|
||||
@param [in] loop_secs= (1) Seconds to wait between each lock attempt
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_abort.sas
|
||||
@li mp_lockfilecheck.sas
|
||||
@li mf_getuser.sas
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_lockanytable.test.sas
|
||||
|
||||
@version 9.2
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_lockanytable(
|
||||
action
|
||||
,lib= WORK
|
||||
,ds=0
|
||||
,ref=
|
||||
,ctl_ds=0
|
||||
,loops=25
|
||||
,loop_secs=1
|
||||
);
|
||||
data _null_;
|
||||
if _n_=1 then putlog "&sysmacroname entry vars:";
|
||||
set sashelp.vmacro;
|
||||
where scope="&sysmacroname";
|
||||
put name '=' value;
|
||||
run;
|
||||
|
||||
%mp_abort(iftrue= (&ds=0 and &action ne MAKETABLE)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(dataset was not provided)
|
||||
)
|
||||
%mp_abort(iftrue= (&ctl_ds=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Control dataset was not provided)
|
||||
)
|
||||
|
||||
/* set up lib & mac vars */
|
||||
%let lib=%upcase(&lib);
|
||||
%let ds=%upcase(&ds);
|
||||
%let action=%upcase(&action);
|
||||
%local user x trans msg abortme;
|
||||
%let user=%mf_getuser();
|
||||
%let abortme=0;
|
||||
|
||||
%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Invalid action (&action) provided)
|
||||
)
|
||||
|
||||
/* if an err condition exists, exit before we even begin */
|
||||
%mp_abort(iftrue= (&syscc>0 and &action=LOCK)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(aborting due to syscc=&syscc on LOCK entry)
|
||||
)
|
||||
|
||||
/* do not bother locking work tables (else may affect all WORK libraries) */
|
||||
%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do;
|
||||
%put NOTE: WORK libraries will not be registered in the locking system.;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* do not proceed if no observations can be processed */
|
||||
%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(options obs = 0. syserrortext=&syserrortext)
|
||||
)
|
||||
|
||||
%if &ACTION=LOCK %then %do;
|
||||
|
||||
/* abort if a SAS lock is already in place, or cannot be applied */
|
||||
%mp_lockfilecheck(&lib..&ds)
|
||||
|
||||
/* next, check there is a record for this table */
|
||||
%local record_exists_check;
|
||||
proc sql noprint;
|
||||
select count(*) into: record_exists_check from &ctl_ds
|
||||
where LOCK_LIB ="&lib" and LOCK_DS="&ds";
|
||||
quit;
|
||||
%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;
|
||||
%if &record_exists_check=0 %then %do;
|
||||
data _null_;
|
||||
putlog "&sysmacroname: adding record to lock table..";
|
||||
run;
|
||||
|
||||
data ;
|
||||
if 0 then set &ctl_ds;
|
||||
LOCK_LIB ="&lib";
|
||||
LOCK_DS="&ds";
|
||||
LOCK_STATUS_CD='LOCKED';
|
||||
LOCK_START_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt;
|
||||
LOCK_USER_NM="&user";
|
||||
LOCK_PID="&sysjobid";
|
||||
LOCK_REF="&ref";
|
||||
output;stop;
|
||||
run;
|
||||
%let trans=&syslast;
|
||||
proc append base=&ctl_ds data=&trans;
|
||||
run;
|
||||
%end;
|
||||
/* if record does exist, perform lock attempts */
|
||||
%else %do x=1 %to &loops;
|
||||
data _null_;
|
||||
putlog "&sysmacroname: attempting lock (iteration &x) "@;
|
||||
putlog "at %sysfunc(datetime(),datetime19.) ..";
|
||||
run;
|
||||
|
||||
proc sql;
|
||||
update &ctl_ds
|
||||
set LOCK_STATUS_CD='LOCKED'
|
||||
, LOCK_START_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt
|
||||
, LOCK_USER_NM="&user"
|
||||
, LOCK_PID="&sysjobid"
|
||||
, LOCK_REF="&ref"
|
||||
where LOCK_LIB ="&lib" and LOCK_DS="&ds";
|
||||
quit;
|
||||
/**
|
||||
* NOTE - occasionally SQL server will return an err code (deadlocked
|
||||
* transaction). If so, ignore it, keep calm, and carry on..
|
||||
*/
|
||||
%if &syscc>0 %then %do;
|
||||
data _null_;
|
||||
putlog 'NOTE-' / 'NOTE-';
|
||||
putlog "NOTE- &sysmacroname: Update failed. "@;
|
||||
putlog "Resetting err conditions and re-attempting.";
|
||||
putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc";
|
||||
putlog 'NOTE-' / 'NOTE-';
|
||||
run;
|
||||
%let syscc=0;
|
||||
%let sqlrc=0;
|
||||
%end;
|
||||
|
||||
/* now check if the record was successfully updated */
|
||||
%local success_check;
|
||||
proc sql noprint;
|
||||
select count(*) into: success_check from &ctl_ds
|
||||
where LOCK_LIB ="&lib" and LOCK_DS="&ds"
|
||||
and LOCK_PID="&sysjobid" and LOCK_STATUS_CD='LOCKED';
|
||||
quit;
|
||||
%if &success_check=0 %then %do;
|
||||
%if &x < &loops %then %do;
|
||||
/* pause before next check */
|
||||
data _null_;
|
||||
putlog 'NOTE-' / 'NOTE-';
|
||||
putlog "NOTE- &sysmacroname: table locked, waiting "@;
|
||||
putlog "%sysfunc(sleep(&loop_inc)) seconds.. ";
|
||||
putlog "NOTE- (iteration &x of &loops)";
|
||||
putlog 'NOTE-' / 'NOTE-';
|
||||
run;
|
||||
%end;
|
||||
%else %do;
|
||||
%let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n
|
||||
Please ask your administrator to investigate!;
|
||||
%let abortme=1;
|
||||
%end;
|
||||
%end;
|
||||
%else %do;
|
||||
data _null_;
|
||||
putlog 'NOTE-' / 'NOTE-';
|
||||
putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@
|
||||
putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;
|
||||
putlog 'NOTE-' / 'NOTE-';
|
||||
run;
|
||||
%if &syscc>0 %then %do;
|
||||
%put setting syscc(&syscc) back to 0;
|
||||
%let syscc=0;
|
||||
%end;
|
||||
%let x=&loops; /* no more iterations needed */
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
%else %if &ACTION=UNLOCK %then %do;
|
||||
%local status;
|
||||
proc sql noprint;
|
||||
select LOCK_STATUS_CD into: status from &ctl_ds
|
||||
where LOCK_LIB ="&lib" and LOCK_DS="&ds";
|
||||
quit;
|
||||
%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;
|
||||
%if &status=LOCKED %then %do;
|
||||
data _null_;
|
||||
putlog "&sysmacroname: unlocking &lib..&ds:";
|
||||
run;
|
||||
proc sql;
|
||||
update &ctl_ds
|
||||
set LOCK_STATUS_CD='UNLOCKED'
|
||||
, LOCK_END_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt
|
||||
, LOCK_USER_NM="&user"
|
||||
, LOCK_PID="&sysjobid"
|
||||
, LOCK_REF="&ref"
|
||||
where LOCK_LIB ="&lib" and LOCK_DS="&ds";
|
||||
quit;
|
||||
%end;
|
||||
%else %if &status=UNLOCKED %then %do;
|
||||
%put %str(WAR)NING: &lib..&ds is already unlocked!;
|
||||
%end;
|
||||
%else %do;
|
||||
%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;
|
||||
%let abortme=1;
|
||||
%end;
|
||||
%end;
|
||||
%else %if &action=MAKETABLE %then %do;
|
||||
proc sql;
|
||||
create table &ctl_ds(
|
||||
lock_lib char(8),
|
||||
lock_ds char(32),
|
||||
lock_status_cd char(10) not null,
|
||||
lock_user_nm char(100) not null ,
|
||||
lock_ref char(200),
|
||||
lock_pid char(10),
|
||||
lock_start_dttm num format=E8601DT26.6,
|
||||
lock_end_dttm num format=E8601DT26.6,
|
||||
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds));
|
||||
%end;
|
||||
%else %do;
|
||||
%let msg=lock_anytable given unsupported action (&action);
|
||||
%let abortme=1;
|
||||
%end;
|
||||
|
||||
/* catch errors - mp_abort must be called outside of a logic block */
|
||||
%mp_abort(iftrue=(&abortme=1),
|
||||
msg=%superq(msg),
|
||||
mac=&sysmacroname
|
||||
)
|
||||
|
||||
%exit_macro:
|
||||
data _null_;
|
||||
put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds";
|
||||
put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr";
|
||||
run;
|
||||
%mend mp_lockanytable;
|
||||
|
||||
|
||||
97
base/mp_lockfilecheck.sas
Normal file
97
base/mp_lockfilecheck.sas
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
@file
|
||||
@brief Aborts if a SAS lock file is in place, or if one cannot be applied.
|
||||
@details Used in conjuction with the mp_lockanytable macro.
|
||||
More info here: https://sasensei.com/flash/24
|
||||
|
||||
Usage:
|
||||
|
||||
data work.test; a=1;run;
|
||||
%mp_lockfilecheck(work.test)
|
||||
|
||||
@param [in] libds The libref.dataset for which to check the lock status
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_abort.sas
|
||||
@li mf_getattrc.sas
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_lockanytable.sas
|
||||
@li mp_lockfilecheck.test.sas
|
||||
|
||||
@version 9.2
|
||||
**/
|
||||
|
||||
%macro mp_lockfilecheck(
|
||||
libds
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
data _null_;
|
||||
if _n_=1 then putlog "&sysmacroname entry vars:";
|
||||
set sashelp.vmacro;
|
||||
where scope="&sysmacroname";
|
||||
put name '=' value;
|
||||
run;
|
||||
|
||||
%mp_abort(iftrue= (&syscc>0)
|
||||
,mac=checklock.sas
|
||||
,msg=Aborting with syscc=&syscc on entry.
|
||||
)
|
||||
%mp_abort(iftrue= (&libds=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(libds not provided)
|
||||
)
|
||||
|
||||
%local msg lib ds;
|
||||
%let lib=%upcase(%scan(&libds,1,.));
|
||||
%let ds=%upcase(%scan(&libds,2,.));
|
||||
|
||||
/* do not proceed if no observations can be processed */
|
||||
%let msg=options obs = 0. syserrortext=%superq(syserrortext);
|
||||
%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)
|
||||
,mac=checklock.sas
|
||||
,msg=%superq(msg)
|
||||
)
|
||||
|
||||
data _null_;
|
||||
putlog "Checking engine & member type";
|
||||
run;
|
||||
%local engine memtype;
|
||||
%let memtype=%mf_getattrc(&libds,MTYPE);
|
||||
%let engine=%mf_getattrc(&libds,ENGINE);
|
||||
|
||||
%if &engine ne V9 and &engine ne BASE %then %do;
|
||||
data _null_;
|
||||
putlog "Lib &lib is not assigned using BASE engine - uses &engine instead";
|
||||
putlog "SAS lock check will not be performed";
|
||||
run;
|
||||
%return;
|
||||
%end;
|
||||
%else %if &memtype ne DATA %then %do;
|
||||
%put NOTE: Cannot lock a VIEW!! Memtype=&memtype;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
data _null_;
|
||||
putlog "Engine = &engine, memtype=&memtype";
|
||||
putlog "Attempting lock statement";
|
||||
run;
|
||||
|
||||
lock &libds;
|
||||
|
||||
%local abortme;
|
||||
%let abortme=0;
|
||||
%if &syscc>0 or &SYSLCKRC ne 0 %then %do;
|
||||
%let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc);
|
||||
%put %str(ERR)OR: &sysmacroname: &msg;
|
||||
%let abortme=1;
|
||||
%end;
|
||||
|
||||
lock &libds clear;
|
||||
|
||||
%mp_abort(iftrue= (&abortme=1)
|
||||
,mac=&sysmacroname
|
||||
,msg=%superq(msg)
|
||||
)
|
||||
|
||||
%mend mp_lockfilecheck;
|
||||
@@ -14,14 +14,14 @@
|
||||
We take the standard definition one step further by embedding the informat
|
||||
in the table header row, like so:
|
||||
|
||||
|var1:$|var2:best.|var3:date9.|
|
||||
|var1:$32|var2:best.|var3:date9.|
|
||||
|---|---|---|
|
||||
|some text|42|01JAN1960|
|
||||
|blah|1|31DEC1999|
|
||||
|
||||
Which resolves to:
|
||||
|
||||
|var1:$|var2:best.|var3:date9.|
|
||||
|var1:$32|var2:best.|var3:date9.|
|
||||
|---|---|---|
|
||||
|some text|42|01JAN1960|
|
||||
|blah|1|31DEC1999|
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
@file mp_unzip.sas
|
||||
@brief Unzips a zip file
|
||||
@details Opens the zip file and copies all the contents to another directory.
|
||||
It is not possible to retain permissions / timestamps, also the BOF marker
|
||||
is lost so it cannot extract binary files.
|
||||
It is not possible to retain permissions / timestamps, also the BOF marker
|
||||
is lost so it cannot extract binary files.
|
||||
|
||||
Usage:
|
||||
Usage:
|
||||
|
||||
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||
%inc mc;
|
||||
@@ -16,8 +16,9 @@
|
||||
@li mf_mkdir.sas
|
||||
@li mf_getuniquefileref.sas
|
||||
|
||||
@param ziploc= fileref or quoted full path to zip file ("/path/to/file.zip")
|
||||
@param outdir= directory in which to write the outputs (created if non existant)
|
||||
@param ziploc= Fileref or quoted full path to zip file ("/path/to/file.zip")
|
||||
@param outdir= (%sysfunc(pathname(work))) Directory in which to write the
|
||||
outputs (created if non existant)
|
||||
|
||||
@version 9.4
|
||||
@author Allan Bowe
|
||||
@@ -35,7 +36,8 @@
|
||||
%let fname2=%mf_getuniquefileref();
|
||||
%let fname3=%mf_getuniquefileref();
|
||||
|
||||
filename &fname1 ZIP &ziploc; * Macro variable &datazip would be read from the file*;
|
||||
/* Macro variable &datazip would be read from the file */
|
||||
filename &fname1 ZIP &ziploc;
|
||||
|
||||
/* Read the "members" (files) from the ZIP file */
|
||||
data _data_(keep=memname isFolder);
|
||||
|
||||
37
base/mp_wait4file.sas
Normal file
37
base/mp_wait4file.sas
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
@file
|
||||
@brief Wait until a file arrives before continuing execution
|
||||
@details Loops with a `sleep()` command until a file arrives or the max wait
|
||||
period expires.
|
||||
|
||||
@example
|
||||
|
||||
Wait 3 minutes OR for /tmp/flag.txt to appear
|
||||
|
||||
%mp_wait4file(/tmp/flag.txt , maxwait=60*3)
|
||||
|
||||
@param [in] file The file to wait for. Must be provided.
|
||||
@param [in] maxwait= (0) Number of seconds to wait. If set to zero, will
|
||||
loop indefinitely (to a maximum of 46 days, per SAS [documentation](
|
||||
https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a001418809.htm
|
||||
)). Otherwise, execution will proceed upon sleep expiry.
|
||||
@param [in] interval= (1) The wait period between sleeps, in seconds
|
||||
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_wait4file(file, maxwait=0, interval=1);
|
||||
|
||||
%if %str(&file)=%str() %then %do;
|
||||
%put %str(ERR)OR: file not provided;
|
||||
%end;
|
||||
|
||||
data _null_;
|
||||
maxwait=&maxwait;
|
||||
if maxwait=0 then maxwait=60*60*24*46;
|
||||
do until (fileexist("&file") or slept>maxwait );
|
||||
slept=sum(slept,sleep(&interval,1));
|
||||
end;
|
||||
run;
|
||||
|
||||
%mend mp_wait4file;
|
||||
59
base/mp_webin.sas
Normal file
59
base/mp_webin.sas
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
@file
|
||||
@brief Fix the `_WEBIN` variables provided to SAS web services
|
||||
@details When uploading files to SAS Stored Processes or Viya Jobs a number
|
||||
of global macro variables are automatically created - however there are some
|
||||
differences in behaviour both between SAS 9 and Viya, and also between a
|
||||
single file upload and a multi-file upload.
|
||||
|
||||
This macro "straightens" up the global macro variables to make it easier /
|
||||
simpler to write code that works in both environments and with a variable
|
||||
number of file inputs.
|
||||
|
||||
After running this macro, the following global variables will *always* exist:
|
||||
@li `_WEBIN_FILE_COUNT`
|
||||
@li `_WEBIN_FILENAME1`
|
||||
@li `_WEBIN_FILEREF1`
|
||||
@li `_WEBIN_NAME1`
|
||||
|
||||
Usage:
|
||||
|
||||
%mp_webin()
|
||||
|
||||
This was created as a macro procedure (over a macro function) as it will also
|
||||
use the filename statement in Viya environments (where `_webin_fileuri` is
|
||||
provided).
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getplatform.sas
|
||||
@li mf_getuniquefileref.sas
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_webin();
|
||||
|
||||
/* prepare global variables */
|
||||
%global _webin_file_count
|
||||
_webin_filename _webin_filename1
|
||||
_webin_fileref _webin_fileref1
|
||||
_webin_fileuri _webin_fileuri1
|
||||
_webin_name _webin_name1
|
||||
;
|
||||
|
||||
/* create initial versions */
|
||||
%let _webin_file_count=%eval(&_webin_file_count+0);
|
||||
%let _webin_filename1=%sysfunc(coalescec(&_webin_filename1,&_webin_filename));
|
||||
%let _webin_fileref1=%sysfunc(coalescec(&_webin_fileref1,&_webin_fileref));
|
||||
%let _webin_fileuri1=%sysfunc(coalescec(&_webin_fileuri1,&_webin_fileuri));
|
||||
%let _webin_name1=%sysfunc(coalescec(&_webin_name1,&_webin_name));
|
||||
|
||||
|
||||
/* If Viya, create temporary fileref(s) */
|
||||
%local i;
|
||||
%if %mf_getplatform()=SASVIYA %then %do i=1 %to &_webin_file_count;
|
||||
%let _webin_fileref&i=%mf_getuniquefileref();
|
||||
filename &&_webin_fileref&i filesrvc "&&_webin_fileuri&i";
|
||||
%end;
|
||||
|
||||
|
||||
%mend mp_webin;
|
||||
@@ -16,11 +16,18 @@
|
||||
@li mp_dirlist.sas
|
||||
|
||||
@param in= unquoted filepath, dataset of files or directory to zip
|
||||
@param type= FILE, DATASET, DIRECTORY. (FILE / DATASET not ready yet)
|
||||
@param outname= output file to create, without .zip extension
|
||||
@param outpath= location for output zip file
|
||||
@param type= (FILE) Valid values:
|
||||
@li FILE - /full/path/and/filename.extension to a particular file
|
||||
@li DATASET - a dataset containing a list of files to zip (see `incol`)
|
||||
@li DIRECTORY - a directory to zip
|
||||
@param outname= (FILE) Output file to create, _without_ .zip extension
|
||||
@param outpath= (%sysfunc(pathname(WORK))) Parent folder for output zip file
|
||||
@param incol= if DATASET input, say which column contains the filepath
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_unzip.sas
|
||||
@li mp_zip.test.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@source https://github.com/sasjs/core
|
||||
@@ -51,9 +58,9 @@ ods package open nopf;
|
||||
set &ds;
|
||||
length __command $4000;
|
||||
if file_or_folder='file';
|
||||
command=cats('ods package add file="',filepath
|
||||
__command=cats('ods package add file="',filepath
|
||||
,'" mimetype="application/x-compress";');
|
||||
call execute(command);
|
||||
call execute(__command);
|
||||
run;
|
||||
/* tidy up */
|
||||
%if &debug=NO %then %do;
|
||||
@@ -64,11 +71,10 @@ ods package open nopf;
|
||||
data _null_;
|
||||
set ∈
|
||||
length __command $4000;
|
||||
command=cats('ods package add file="',&incol
|
||||
__command=cats('ods package add file="',&incol
|
||||
,'" mimetype="application/x-compress";');
|
||||
call execute(command);
|
||||
call execute(__command);
|
||||
run;
|
||||
ods package add file="&in" mimetype="application/x-compress";
|
||||
%end;
|
||||
|
||||
|
||||
|
||||
2
build.py
2
build.py
@@ -22,7 +22,7 @@ for file in files:
|
||||
for line in infile:
|
||||
ml.write(" put '" + line.rstrip().replace("'","''") + " ';\n")
|
||||
ml.write("run;\n\n")
|
||||
ml.write("%inc \"%sysfunc(pathname(work))/" + name + ".lua\";\n\n")
|
||||
ml.write("%inc \"%sysfunc(pathname(work))/" + name + ".lua\" /source2;\n\n")
|
||||
ml.write("%mend " + name + ";\n")
|
||||
|
||||
ml.close()
|
||||
|
||||
25
lua/gsubfile.lua
Normal file
25
lua/gsubfile.lua
Normal file
@@ -0,0 +1,25 @@
|
||||
local fpath, outpath, file, fcontent
|
||||
|
||||
-- configure in / out paths
|
||||
fpath = sas.symget("file")
|
||||
outpath = sas.symget("outfile")
|
||||
if ( outpath == 0 )
|
||||
then
|
||||
outpath=fpath
|
||||
end
|
||||
|
||||
-- open file and perform the substitution
|
||||
file = io.open(fpath,"r")
|
||||
fcontent = file:read("*all")
|
||||
file:close()
|
||||
fcontent = string.gsub(
|
||||
fcontent,
|
||||
sas.symget(sas.symget("patternvar")),
|
||||
sas.symget(sas.symget("replacevar"))
|
||||
)
|
||||
|
||||
-- write the file back out
|
||||
file = io.open(outpath, "w+")
|
||||
io.output(file)
|
||||
io.write(fcontent)
|
||||
io.close(file)
|
||||
44
lua/ml_gsubfile.sas
Normal file
44
lua/ml_gsubfile.sas
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
@file ml_gsubfile.sas
|
||||
@brief Compiles the gsubfile.lua lua file
|
||||
@details Writes gsubfile.lua to the work directory
|
||||
and then includes it.
|
||||
Usage:
|
||||
|
||||
%ml_gsubfile()
|
||||
|
||||
**/
|
||||
|
||||
%macro ml_gsubfile();
|
||||
data _null_;
|
||||
file "%sysfunc(pathname(work))/ml_gsubfile.lua";
|
||||
put 'local fpath, outpath, file, fcontent ';
|
||||
put ' ';
|
||||
put '-- configure in / out paths ';
|
||||
put 'fpath = sas.symget("file") ';
|
||||
put 'outpath = sas.symget("outfile") ';
|
||||
put 'if ( outpath == 0 ) ';
|
||||
put 'then ';
|
||||
put ' outpath=fpath ';
|
||||
put 'end ';
|
||||
put ' ';
|
||||
put '-- open file and perform the substitution ';
|
||||
put 'file = io.open(fpath,"r") ';
|
||||
put 'fcontent = file:read("*all") ';
|
||||
put 'file:close() ';
|
||||
put 'fcontent = string.gsub( ';
|
||||
put ' fcontent, ';
|
||||
put ' sas.symget(sas.symget("patternvar")), ';
|
||||
put ' sas.symget(sas.symget("replacevar")) ';
|
||||
put ') ';
|
||||
put ' ';
|
||||
put '-- write the file back out ';
|
||||
put 'file = io.open(outpath, "w+") ';
|
||||
put 'io.output(file) ';
|
||||
put 'io.write(fcontent) ';
|
||||
put 'io.close(file) ';
|
||||
run;
|
||||
|
||||
%inc "%sysfunc(pathname(work))/ml_gsubfile.lua" /source2;
|
||||
|
||||
%mend ml_gsubfile;
|
||||
@@ -389,6 +389,6 @@ data _null_;
|
||||
put '-- JSON.LUA ENDS HERE ';
|
||||
run;
|
||||
|
||||
%inc "%sysfunc(pathname(work))/ml_json.lua";
|
||||
%inc "%sysfunc(pathname(work))/ml_json.lua" /source2;
|
||||
|
||||
%mend ml_json;
|
||||
|
||||
@@ -385,6 +385,7 @@ data _null_;
|
||||
put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
|
||||
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
|
||||
put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
|
||||
put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
|
||||
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
|
||||
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
|
||||
put ' put '',"SYSVLONG" : '' sysvlong; ';
|
||||
|
||||
@@ -155,6 +155,7 @@
|
||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||
put ",""SYSSITE"" : ""&syssite"" ";
|
||||
sysvlong=quote(trim(symget('sysvlong')));
|
||||
put ',"SYSVLONG" : ' sysvlong;
|
||||
|
||||
606
package-lock.json
generated
606
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,6 @@
|
||||
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks || true"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sasjs/cli": "^2.37.2"
|
||||
"@sasjs/cli": "^2.39.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,10 @@
|
||||
"appLoc": "/Shared Data/temp/macrocore",
|
||||
"macroFolders": [
|
||||
"tests/sas9only"
|
||||
]
|
||||
],
|
||||
"deployConfig": {
|
||||
"deployServicePack": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "docsonly",
|
||||
|
||||
@@ -38,4 +38,12 @@
|
||||
),
|
||||
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
|
||||
)
|
||||
36
tests/crossplatform/mf_getuniquefileref.test.sas
Normal file
36
tests/crossplatform/mf_getuniquefileref.test.sas
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mf_getuniquefileref macro
|
||||
@details To test performance you can also use the following macro:
|
||||
|
||||
%macro x(prefix);
|
||||
%let now=%sysfunc(datetime());
|
||||
%do x=1 %to 1000;
|
||||
%let rc=%mf_getuniquefileref(prefix=&prefix);
|
||||
%end;
|
||||
%put %sysevalf(%sysfunc(datetime())-&now);
|
||||
%mend;
|
||||
%x(_)
|
||||
%x(0)
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquefileref.sas
|
||||
@li mp_assert.sas
|
||||
|
||||
**/
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(
|
||||
"%substr(%mf_getuniquefileref(prefix=0),1,1)"="#"
|
||||
),
|
||||
desc=Checking for a natively assigned fileref,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(
|
||||
"%substr(%mf_getuniquefileref(),1,1)"="_"
|
||||
),
|
||||
desc=Checking for a default fileref,
|
||||
outds=work.test_results
|
||||
)
|
||||
41
tests/crossplatform/mp_appendfile.test.sas
Normal file
41
tests/crossplatform/mp_appendfile.test.sas
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_appendfile.sas macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_appendfile.sas
|
||||
@li mp_assert.sas
|
||||
|
||||
**/
|
||||
|
||||
|
||||
filename tmp1 temp;
|
||||
filename tmp2 temp;
|
||||
filename tmp3 temp;
|
||||
data _null_; file tmp1; put 'base file';
|
||||
data _null_; file tmp2; put 'append1';
|
||||
data _null_; file tmp3; put 'append2';
|
||||
run;
|
||||
%mp_appendfile(baseref=tmp1, appendrefs=tmp2 tmp3)
|
||||
data _null_;
|
||||
infile tmp1;
|
||||
input;
|
||||
put _infile_;
|
||||
call symputx(cats('check',_n_),_infile_);
|
||||
run;
|
||||
%global check1 check2 check3;
|
||||
%mp_assert(
|
||||
iftrue=("&check1"="base file"),
|
||||
desc=Line 1 of file tmp1 is correct,
|
||||
outds=work.test_results
|
||||
)
|
||||
%mp_assert(
|
||||
iftrue=("&check2"="append1"),
|
||||
desc=Line 2 of file tmp1 is correct,
|
||||
outds=work.test_results
|
||||
)
|
||||
%mp_assert(
|
||||
iftrue=("&check3"="append2"),
|
||||
desc=Line 3 of file tmp1 is correct,
|
||||
outds=work.test_results
|
||||
)
|
||||
99
tests/crossplatform/mp_binarycopy.test.sas
Normal file
99
tests/crossplatform/mp_binarycopy.test.sas
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_binarycopy.sas macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_binarycopy.sas
|
||||
@li mp_assert.sas
|
||||
|
||||
**/
|
||||
|
||||
|
||||
/* TEST 1 - regular file copy */
|
||||
%let string1=test1;
|
||||
filename tmp temp;
|
||||
filename myref temp;
|
||||
data _null_;
|
||||
file tmp;
|
||||
put "&string1";
|
||||
run;
|
||||
%mp_binarycopy(inref=tmp, outref=myref)
|
||||
data _null_;
|
||||
infile myref;
|
||||
input;
|
||||
put _infile_;
|
||||
call symputx('string1_check',_infile_);
|
||||
stop;
|
||||
run;
|
||||
%mp_assert(
|
||||
iftrue=("&string1"="&string1_check"),
|
||||
desc=Basic String Compare,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
|
||||
/* TEST 2 - File append */
|
||||
%let string2=test2;
|
||||
%let path2=%sysfunc(pathname(work))/somefile.txt;
|
||||
data _null_;
|
||||
file "&path2";
|
||||
put "&string2";
|
||||
run;
|
||||
%mp_binarycopy(inloc="&path2", outref=myref, mode=APPEND)
|
||||
data _null_;
|
||||
infile myref;
|
||||
input;
|
||||
put _infile_;
|
||||
if _n_=2 then call symputx('string2_check',_infile_);
|
||||
run;
|
||||
%mp_assert(
|
||||
iftrue=("&string2"="&string2_check"),
|
||||
desc=Append Check (file to ref),
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
/* TEST 3 - File create (ref to existing file) */
|
||||
%let string3=test3;
|
||||
%let path3=%sysfunc(pathname(work))/somefile3.txt;
|
||||
filename tmp3 temp;
|
||||
data _null_;
|
||||
file tmp3;
|
||||
put "&string3";
|
||||
run;
|
||||
data _null_;
|
||||
file "&path3";
|
||||
put "this should not be returned";
|
||||
run;
|
||||
%mp_binarycopy(inref=tmp3, outloc="&path3")
|
||||
data _null_;
|
||||
infile "&path3";
|
||||
input;
|
||||
put _infile_;
|
||||
if _n_=1 then call symputx('string3_check',_infile_);
|
||||
run;
|
||||
%mp_assert(
|
||||
iftrue=("&string3"="&string3_check"),
|
||||
desc=Append Check (ref to existing file),
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
/* TEST 4 - File append (ref to file) */
|
||||
%let string4=test4;
|
||||
%let string4_check=;
|
||||
filename tmp4 temp;
|
||||
data _null_;
|
||||
file tmp4;
|
||||
put "&string4";
|
||||
run;
|
||||
%mp_binarycopy(inref=tmp4, outloc="&path3",mode=APPEND)
|
||||
data _null_;
|
||||
infile "&path3";
|
||||
input;
|
||||
put _infile_;
|
||||
if _n_=2 then call symputx('string4_check',_infile_);
|
||||
run;
|
||||
%mp_assert(
|
||||
iftrue=("&string4"="&string4_check"),
|
||||
desc=Append Check (ref to file),
|
||||
outds=work.test_results
|
||||
)
|
||||
52
tests/crossplatform/mp_copyfolder.test.sas
Normal file
52
tests/crossplatform/mp_copyfolder.test.sas
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_copyfolder.sas macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_copyfolder.sas
|
||||
@li mf_mkdir.sas
|
||||
@li mf_nobs.sas
|
||||
@li mp_assert.sas
|
||||
@li mp_dirlist.sas
|
||||
|
||||
**/
|
||||
|
||||
/**
|
||||
* make a directory structure
|
||||
*/
|
||||
|
||||
%let root=%sysfunc(pathname(work))/top;
|
||||
%mf_mkdir(&root)
|
||||
%mf_mkdir(&root/a)
|
||||
%mf_mkdir(&root/b)
|
||||
%mf_mkdir(&root/a/d)
|
||||
%mf_mkdir(&root/a/e)
|
||||
%mf_mkdir(&root/a/e/f)
|
||||
data "&root/a/e/f/ds1.sas7bdat";x=1;
|
||||
data "&root/a/e/ds2.sas7bdat";x=1;
|
||||
data "&root/a/ds3.sas7bdat";x=1;
|
||||
run;
|
||||
|
||||
%mp_dirlist(path=&root, outds=myTable, maxdepth=MAX)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(%mf_nobs(work.mytable)=8),
|
||||
desc=Temp data successfully created,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
/**
|
||||
* copy it
|
||||
*/
|
||||
%let newfolder=%sysfunc(pathname(work))/new;
|
||||
%mp_copyfolder(&root,&newfolder)
|
||||
|
||||
%mp_dirlist(path=&newfolder, outds=work.myTable2, maxdepth=MAX)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(%mf_nobs(work.mytable2)=8),
|
||||
desc=Folder successfully copied,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
|
||||
50
tests/crossplatform/mp_deletefolder.test.sas
Normal file
50
tests/crossplatform/mp_deletefolder.test.sas
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_deletefolder.sas macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_deletefolder.sas
|
||||
@li mf_mkdir.sas
|
||||
@li mf_nobs.sas
|
||||
@li mp_assert.sas
|
||||
@li mp_dirlist.sas
|
||||
|
||||
**/
|
||||
|
||||
/**
|
||||
* make a directory structure
|
||||
*/
|
||||
|
||||
%let root=%sysfunc(pathname(work))/top;
|
||||
%mf_mkdir(&root)
|
||||
%mf_mkdir(&root/a)
|
||||
%mf_mkdir(&root/b)
|
||||
%mf_mkdir(&root/a/d)
|
||||
%mf_mkdir(&root/a/e)
|
||||
%mf_mkdir(&root/a/e/f)
|
||||
data "&root/a/e/f/ds1.sas7bdat";
|
||||
x=1;
|
||||
run;
|
||||
|
||||
%mp_dirlist(path=&root, outds=myTable, maxdepth=MAX)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(%mf_nobs(work.mytable)=6),
|
||||
desc=Temp data successfully created,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
%mp_deletefolder(&root/a)
|
||||
|
||||
%mp_dirlist(path=&root, outds=work.myTable2, maxdepth=MAX)
|
||||
|
||||
data _null_;
|
||||
set work.mytable2;
|
||||
putlog (_all_)(=);
|
||||
run;
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(%mf_nobs(work.mytable2)=1),
|
||||
desc=Subfolder and contents successfully deleted,
|
||||
outds=work.test_results
|
||||
)
|
||||
50
tests/crossplatform/mp_dirlist.test.sas
Normal file
50
tests/crossplatform/mp_dirlist.test.sas
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_dirlist.sas macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_nobs.sas
|
||||
@li mf_mkdir.sas
|
||||
@li mp_dirlist.sas
|
||||
@li mp_assert.sas
|
||||
|
||||
**/
|
||||
|
||||
/**
|
||||
* make a directory structure
|
||||
*/
|
||||
|
||||
%let root=%sysfunc(pathname(work))/top;
|
||||
%mf_mkdir(&root)
|
||||
%mf_mkdir(&root/a)
|
||||
%mf_mkdir(&root/b)
|
||||
%mf_mkdir(&root/a/d)
|
||||
%mf_mkdir(&root/a/e)
|
||||
%mf_mkdir(&root/a/e/f)
|
||||
data "&root/a/e/f/ds1.sas7bdat";
|
||||
x=1;
|
||||
run;
|
||||
|
||||
%mp_dirlist(path=&root, outds=myTable, maxdepth=MAX)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(%mf_nobs(work.mytable)=6),
|
||||
desc=All levels returned,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
%mp_dirlist(path=&root, outds=myTable2, maxdepth=2)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(%mf_nobs(work.mytable2)=5),
|
||||
desc=Top two levels returned,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
%mp_dirlist(path=&root, outds=work.myTable3, maxdepth=0)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(%mf_nobs(work.mytable3)=2),
|
||||
desc=Top level returned,
|
||||
outds=work.test_results
|
||||
)
|
||||
60
tests/crossplatform/mp_ds2cards.test.sas
Normal file
60
tests/crossplatform/mp_ds2cards.test.sas
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_ds2cards.sas macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_ds2cards.sas
|
||||
@li mp_assert.sas
|
||||
|
||||
**/
|
||||
|
||||
/**
|
||||
* test 1 - rebuild an existing dataset
|
||||
* Cars is a great dataset - it contains leading spaces, and formatted numerics
|
||||
*/
|
||||
|
||||
%mp_ds2cards(base_ds=sashelp.cars
|
||||
, tgt_ds=work.test
|
||||
, cards_file= "%sysfunc(pathname(work))/cars.sas"
|
||||
, showlog=NO
|
||||
)
|
||||
%inc "%sysfunc(pathname(work))/cars.sas"/source2;
|
||||
|
||||
proc compare base=sashelp.cars compare=work.test;
|
||||
quit;
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(&sysinfo=1),
|
||||
desc=sashelp.cars is identical except for ds label,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
/**
|
||||
* test 2 - binary data compare
|
||||
*/
|
||||
data work.binarybase;
|
||||
format bin $hex500. z $hex.;
|
||||
do x=1 to 250;
|
||||
z=byte(x);
|
||||
bin=trim(bin)!!z;
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
|
||||
%mp_ds2cards(base_ds=work.binarybase
|
||||
, showlog=YES
|
||||
, cards_file="%sysfunc(pathname(work))/c2.sas"
|
||||
, tgt_ds=work.binarycompare
|
||||
, append=
|
||||
)
|
||||
|
||||
%inc "%sysfunc(pathname(work))/c2.sas"/source2;
|
||||
|
||||
proc compare base=work.binarybase compare=work.binarycompare;
|
||||
run;
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(&sysinfo=0),
|
||||
desc=work.binarybase dataset is identical,
|
||||
outds=work.test_results
|
||||
)
|
||||
33
tests/crossplatform/mp_getcols.test.sas
Normal file
33
tests/crossplatform/mp_getcols.test.sas
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_getcols macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_getcols.sas
|
||||
@li mp_assertcolvals.sas
|
||||
@li mp_assertdsobs.sas
|
||||
|
||||
**/
|
||||
|
||||
|
||||
/* valid filter */
|
||||
%mp_getcols(sashelp.airline,outds=work.info)
|
||||
|
||||
|
||||
%mp_assertdsobs(work.info,
|
||||
desc=Has 3 records,
|
||||
test=EQUALS 3,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
data work.check;
|
||||
length val $10;
|
||||
do val='NUMERIC','DATE','CHARACTER';
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
%mp_assertcolvals(work.info.ddtype,
|
||||
checkvals=work.check.val,
|
||||
desc=All values have a match,
|
||||
test=ALLVALS
|
||||
)
|
||||
29
tests/crossplatform/mp_getconstraints.test.sas
Normal file
29
tests/crossplatform/mp_getconstraints.test.sas
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_getconstraints.sas macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_nobs.sas
|
||||
@li mp_getconstraints.sas
|
||||
@li mp_assert.sas
|
||||
|
||||
**/
|
||||
|
||||
proc sql;
|
||||
create table work.example(
|
||||
TX_FROM float format=datetime19.,
|
||||
DD_TYPE char(16),
|
||||
DD_SOURCE char(2048),
|
||||
DD_SHORTDESC char(256),
|
||||
constraint pk primary key(tx_from, dd_type,dd_source),
|
||||
constraint unq unique(tx_from, dd_type),
|
||||
constraint nnn not null(DD_SHORTDESC)
|
||||
);
|
||||
|
||||
%mp_getconstraints(lib=work,ds=example,outds=work.constraints)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(%mf_nobs(work.constraints)=6),
|
||||
desc=Output table work.constraints created with correct number of records,
|
||||
outds=work.test_results
|
||||
)
|
||||
66
tests/crossplatform/mp_gsubfile.test.sas
Normal file
66
tests/crossplatform/mp_gsubfile.test.sas
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_gsubfile.sas macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_gsubfile.sas
|
||||
@li mp_assert.sas
|
||||
|
||||
**/
|
||||
|
||||
/**
|
||||
* test 1 - simple replace
|
||||
*/
|
||||
%global str1;
|
||||
%let file=%sysfunc(pathname(work))/file.txt;
|
||||
%let pat=replace/me;
|
||||
%let str=with/this;
|
||||
data _null_;
|
||||
file "&file";
|
||||
put "&pat";
|
||||
run;
|
||||
%mp_gsubfile(file=&file, patternvar=pat, replacevar=str)
|
||||
data _null_;
|
||||
infile "&file";
|
||||
input;
|
||||
call symputx('str1',_infile_);
|
||||
run;
|
||||
|
||||
%mp_assert(
|
||||
iftrue=("&str1"="&str"),
|
||||
desc=Check that simple replacement was successful,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
/**
|
||||
* test 2 - replace from additional line
|
||||
*/
|
||||
%global str2 strcheck2 strcheck2b;
|
||||
%let file2=%sysfunc(pathname(work))/file2.txt;
|
||||
%let pat2=replace/me;
|
||||
%let str2=with/this;
|
||||
data _null_;
|
||||
file "&file2";
|
||||
put 'line1';output;
|
||||
put "&pat2";output;
|
||||
put "&pat2";output;
|
||||
run;
|
||||
%mp_gsubfile(file=&file2, patternvar=pat2, replacevar=str2)
|
||||
data _null_;
|
||||
infile "&file2";
|
||||
input;
|
||||
if _n_=2 then call symputx('strcheck2',_infile_);
|
||||
if _n_=3 then call symputx('strcheck2b',_infile_);
|
||||
putlog _infile_;
|
||||
run;
|
||||
|
||||
%mp_assert(
|
||||
iftrue=("&strcheck2"="&str2"),
|
||||
desc=Check that multi line replacement was successful (line2),
|
||||
outds=work.test_results
|
||||
)
|
||||
%mp_assert(
|
||||
iftrue=("&strcheck2b"="&str2"),
|
||||
desc=Check that multi line replacement was successful (line3),
|
||||
outds=work.test_results
|
||||
)
|
||||
62
tests/crossplatform/mp_lockanytable.test.sas
Normal file
62
tests/crossplatform/mp_lockanytable.test.sas
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_lockfilecheck macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_lockanytable.sas
|
||||
@li mp_assertcols.sas
|
||||
@li mp_assertcolvals.sas
|
||||
|
||||
**/
|
||||
|
||||
/* check create table */
|
||||
|
||||
%mp_lockanytable(MAKETABLE, ctl_ds=work.controller)
|
||||
|
||||
%mp_assertcols(work.controller,
|
||||
cols=lock_status_cd lock_lib lock_ds lock_user_nm lock_ref lock_pid
|
||||
lock_start_dttm lock_end_dttm,
|
||||
test=ALL,
|
||||
desc=check all control columns exist
|
||||
)
|
||||
|
||||
/* check lock table */
|
||||
options dlcreatedir;
|
||||
libname tmp "%sysfunc(pathname(work))/tmp";
|
||||
data tmp.sometable;
|
||||
x=1;
|
||||
run;
|
||||
|
||||
%mp_lockanytable(LOCK,lib=tmp,ds=sometable,ref=This Ref, ctl_ds=work.controller)
|
||||
|
||||
data work.checkds1;
|
||||
checkval='SOMETABLE';
|
||||
run;
|
||||
%mp_assertcolvals(work.controller.lock_ds,
|
||||
checkvals=work.checkds1.checkval,
|
||||
desc=table is captured in lock,
|
||||
test=ANYVAL
|
||||
)
|
||||
|
||||
data work.checkds2;
|
||||
checkval='LOCKED';
|
||||
run;
|
||||
%mp_assertcolvals(work.controller.lock_status_cd,
|
||||
checkvals=work.checkds2.checkval,
|
||||
desc=code is captured in lock,
|
||||
test=ANYVAL
|
||||
)
|
||||
|
||||
|
||||
|
||||
/* check for unsuccessful unlock */
|
||||
%mp_lockanytable(UNLOCK,lib=tmp,ds=sometable,ref=bye, ctl_ds=work.controller)
|
||||
|
||||
data work.checkds3;
|
||||
checkval='UNLOCKED';
|
||||
run;
|
||||
%mp_assertcolvals(work.controller.lock_status_cd,
|
||||
checkvals=work.checkds3.checkval,
|
||||
desc=Ref is captured in unlock,
|
||||
test=ANYVAL
|
||||
)
|
||||
36
tests/crossplatform/mp_lockfilecheck.test.sas
Normal file
36
tests/crossplatform/mp_lockfilecheck.test.sas
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_lockfilecheck macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_lockfilecheck.sas
|
||||
@li mp_assert.sas
|
||||
|
||||
**/
|
||||
|
||||
|
||||
/* check for regular lock */
|
||||
data work.test; a=1;run;
|
||||
%mp_lockfilecheck(work.test)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(&syscc=0),
|
||||
desc=Checking regular table can be locked,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
|
||||
/* check for unsuccessful lock */
|
||||
%global success abortme;
|
||||
%let success=0;
|
||||
%macro mp_abort(iftrue=,mac=,msg=);
|
||||
%if &abortme=1 %then %let success=1;
|
||||
%mend mp_abort;
|
||||
|
||||
%mp_lockfilecheck(sashelp.class)
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(&success=1),
|
||||
desc=Checking sashelp table cannot be locked,
|
||||
outds=work.test_results
|
||||
)
|
||||
39
tests/crossplatform/mp_webin.test.sas
Normal file
39
tests/crossplatform/mp_webin.test.sas
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_webin macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_webin.sas
|
||||
@li mp_assert.sas
|
||||
|
||||
**/
|
||||
|
||||
/* force SAS9 tests as we don't have a valid URI available */
|
||||
%macro mf_getplatform();
|
||||
SAS9
|
||||
%mend mf_getplatform;
|
||||
|
||||
/* TEST 1 */
|
||||
%let _webin_file_count=1;
|
||||
%let _webin_filename=test;
|
||||
%mp_webin()
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(
|
||||
%symexist(_WEBIN_FILEREF1)
|
||||
),
|
||||
desc=Checking if the macvar exists,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
/* TEST 2 */
|
||||
%global _WEBIN_FILENAME1;
|
||||
%mp_assert(
|
||||
iftrue=(
|
||||
%str(&_WEBIN_FILENAME1)=%str(test)
|
||||
),
|
||||
desc=Checking if the macvar exists,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
|
||||
115
tests/crossplatform/mp_zip.test.sas
Normal file
115
tests/crossplatform/mp_zip.test.sas
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
@file
|
||||
@brief Testing mp_zip macro
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_mkdir.sas
|
||||
@li mp_assert.sas
|
||||
@li mp_zip.sas
|
||||
@li mp_unzip.sas
|
||||
|
||||
**/
|
||||
|
||||
%let work=%sysfunc(pathname(work));
|
||||
%let root=&work/zipme;
|
||||
|
||||
/* TEST 1 - zip a file */
|
||||
%mf_mkdir(&root)
|
||||
|
||||
data _null_;
|
||||
file "&root/test.txt";
|
||||
put "houston, this is a test";
|
||||
run;
|
||||
|
||||
%mp_zip(in=&root/test.txt
|
||||
,type=FILE
|
||||
,outpath=&work
|
||||
,outname=myFile
|
||||
)
|
||||
|
||||
%mp_unzip(ziploc="&work/myFile.zip",outdir=&work)
|
||||
|
||||
data _null_;
|
||||
infile "&work/test.txt";
|
||||
input;
|
||||
call symputx('content1',_infile_);
|
||||
putlog _infile_;
|
||||
run;
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(
|
||||
%str(&content1)=%str(houston, this is a test)
|
||||
),
|
||||
desc=Checking if file zip / unzip works,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
/* TEST 2 - zip a dataset of files */
|
||||
data _null_;
|
||||
file "&root/test2.txt";
|
||||
put "houston, this is test2";
|
||||
run;
|
||||
libname tmp "&root";
|
||||
data tmp.test;
|
||||
filepath="&root/test2.txt";
|
||||
run;
|
||||
|
||||
%mp_zip(in=tmp.test
|
||||
,incol=filepath
|
||||
,type=DATASET
|
||||
,outpath=&work
|
||||
,outname=myFile2
|
||||
)
|
||||
|
||||
%mp_unzip(ziploc="&work/myFile2.zip",outdir=&work)
|
||||
|
||||
data _null_;
|
||||
infile "&work/test2.txt";
|
||||
input;
|
||||
call symputx('content2',_infile_);
|
||||
putlog _infile_;
|
||||
run;
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(
|
||||
%str(&content2)=%str(houston, this is test2)
|
||||
),
|
||||
desc=Checking if file zip / unzip from a dataset works,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
/* TEST 3 - zip a dataset of files */
|
||||
%mf_mkdir(&work/out3)
|
||||
|
||||
%mp_zip(in=&root
|
||||
,type=DIRECTORY
|
||||
,outpath=&work
|
||||
,outname=myFile3
|
||||
)
|
||||
|
||||
%mp_unzip(ziploc="&work/myFile3.zip",outdir=&work/out3)
|
||||
|
||||
data _null_;
|
||||
infile "&work/out3/test.txt";
|
||||
input;
|
||||
call symputx('content3a',_infile_);
|
||||
putlog _infile_;
|
||||
run;
|
||||
data _null_;
|
||||
infile "&work/out3/test2.txt";
|
||||
input;
|
||||
call symputx('content3b',_infile_);
|
||||
putlog _infile_;
|
||||
run;
|
||||
|
||||
%mp_assert(
|
||||
iftrue=(
|
||||
%str(&content3a)=%str(houston, this is a test)
|
||||
and
|
||||
%str(&content3b)=%str(houston, this is test2)
|
||||
),
|
||||
desc=Checking if file zip / unzip from a directory works,
|
||||
outds=work.test_results
|
||||
)
|
||||
|
||||
|
||||
@@ -5,4 +5,12 @@
|
||||
**/
|
||||
|
||||
/* location in metadata or SAS Drive for temporary files */
|
||||
%let mcTestAppLoc=/Public/temp/macrocore;
|
||||
%let mcTestAppLoc=/Public/temp/macrocore;
|
||||
|
||||
%macro loglevel();
|
||||
%if &_debug=2477 %then %do;
|
||||
options mprint;
|
||||
%end;
|
||||
%mend loglevel;
|
||||
|
||||
%loglevel()
|
||||
24
tests/viyaonly/mv_getfoldermembers.test.sas
Normal file
24
tests/viyaonly/mv_getfoldermembers.test.sas
Normal 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
|
||||
)
|
||||
@@ -590,6 +590,7 @@ data _null_;
|
||||
put ' put ",""SYSCC"" : ""&syscc"" "; ';
|
||||
put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
|
||||
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
|
||||
put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
|
||||
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
|
||||
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
|
||||
put ' put '',"SYSVLONG" : '' sysvlong; ';
|
||||
|
||||
@@ -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;
|
||||
@@ -109,6 +122,7 @@ options noquotelenmax;
|
||||
run;
|
||||
libname &libref2 JSON fileref=&fname2;
|
||||
data &outds;
|
||||
length id $36 name $128 uri $64 type $32 description $256;
|
||||
set &libref2..items;
|
||||
run;
|
||||
filename &fname2 clear;
|
||||
|
||||
@@ -215,6 +215,7 @@
|
||||
put ",""SYSCC"" : ""&syscc"" ";
|
||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||
put ",""SYSSITE"" : ""&syssite"" ";
|
||||
sysvlong=quote(trim(symget('sysvlong')));
|
||||
put ',"SYSVLONG" : ' sysvlong;
|
||||
|
||||
Reference in New Issue
Block a user