1
0
mirror of https://github.com/sasjs/core.git synced 2025-12-11 06:24:35 +00:00

Compare commits

...

28 Commits

Author SHA1 Message Date
Allan Bowe
914dc51aca Merge pull request #101 from sasjs:fix_copyfolder
fix: correct target path. Closes #100
2021-12-02 13:29:24 +00:00
Allan Bowe
7ce480db6a fix: updating all.sas 2021-12-02 13:29:02 +00:00
Ivor Townsend
3120ba68ad fix: correct target path. Closes #100 2021-12-02 09:49:05 +00:00
Allan Bowe
9eff1e0e83 Merge pull request #99 from sasjs/issue98
feat: adding ms_webout macro for server responses on sasjs/server.
2021-12-01 23:40:07 +00:00
Allan Bowe
678250ba27 fix: tests 2021-11-30 16:39:33 +00:00
Allan Bowe
6845a63196 chore: tidy up repo 2021-11-30 16:31:26 +00:00
Allan Bowe
3103abe3c8 chore: updating readme and dox file 2021-11-30 16:27:04 +00:00
Allan Bowe
318fd1ddde feat: adding ms_webout macro for server responses on sasjs/server. Closes #98 2021-11-30 15:26:02 +00:00
Allan Bowe
7b2b81a501 Merge pull request #97 from sasjs/issue92
Issue92
2021-11-29 11:48:49 +00:00
Allan Bowe
02de4e42bf chore: generating all.sas and fixing indentation 2021-11-29 11:35:37 +00:00
Allan Bowe
ddd831fe7a feat: new copyfolder macro and associated test. Closes #92 2021-11-29 11:35:03 +00:00
Allan Bowe
42a46b32e9 Merge pull request #96 from sasjs/add_deletefolder
feat: Adding Delete Folder Macro
2021-11-26 16:34:03 +00:00
Allan Bowe
3b395b3ae5 chore: all.sas update 2021-11-26 16:20:33 +00:00
Allan Bowe
9e84e47563 feat: mp_deletefolder tests, closes #90 2021-11-26 16:20:13 +00:00
Allan Bowe
aff29427e2 chore: merge main 2021-11-26 15:37:08 +00:00
Ivor Townsend
474f1b3cc6 feat: Adding Delete Folder Macro 2021-11-26 15:06:38 +00:00
Allan Bowe
22bf8065bb Merge pull request #95 from sasjs/dirlist_enhancement
feat: Recursive Directory Listing
2021-11-26 13:00:15 +00:00
Allan Bowe
7bb089e269 fix: moving cleanup to temp table for efficiency 2021-11-26 11:55:55 +00:00
Allan Bowe
a6e9158814 chore: adding recursive option to the mp_dirlist macro, and updating all.sas 2021-11-26 11:52:48 +00:00
Ivor Townsend
19a1336720 feat: Recursive Directory Listing 2021-11-26 09:29:42 +00:00
Allan Bowe
79d5d42e6b Merge pull request #94 from sasjs/issue93
Macros for locking / unlocking tables
2021-11-25 17:44:37 +00:00
Allan Bowe
2d81de5841 chore: generating all.sas and updating doc links 2021-11-25 16:46:56 +00:00
Allan Bowe
107ab836d6 feat: mp_lockXXX macros f
These macros provide a centralised approach for locking tables where updates can happen in multiple passes.  The mp_lockfilecheck macro will also verify the ability to create a SAS lock (if a v9 library engine, and not a view)
2021-11-25 16:43:39 +00:00
Allan Bowe
5225e74465 chore: adding @vznesh for issue #88 2021-11-18 18:40:32 +00:00
Allan Bowe
39253d2828 chore: merge conflicts 2021-11-18 18:36:32 +00:00
Allan Bowe
171c169537 chore: updating all.sas 2021-11-18 18:36:08 +00:00
Allan Bowe
76af9fa33c Merge pull request #89 from sasjs/issue88
fix: removing notes when running mp_zip, closes #88. .
2021-11-18 18:35:33 +00:00
Allan Bowe
37ccc8a2aa fix: removing notes when running mp_zip, closes #88. .
Also adding 3 tests
2021-11-18 18:21:43 +00:00
27 changed files with 1848 additions and 127 deletions

View File

@@ -108,6 +108,15 @@
"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,

View File

@@ -1,12 +0,0 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = false
trim_trailing_whitespace = true

View File

View File

@@ -1,7 +1,6 @@
# Macro Core
[![npm package][npm-image]][npm-url]
[![Github Workflow][githubworkflow-image]][githubworkflow-url]
[![Dependency Status][dependency-image]][dependency-url]
[![npm](https://img.shields.io/npm/dt/@sasjs/core)]()
![Snyk Vulnerabilities for npm package](https://img.shields.io/snyk/vulnerabilities/npm/@sasjs/core)
[![License](https://img.shields.io/apm/l/atomic-design-ui.svg)](/LICENSE)
@@ -16,11 +15,9 @@
[npm-url]:http://npmjs.org/package/@sasjs/core
[githubworkflow-image]:https://github.com/sasjs/core/actions/workflows/main.yml/badge.svg
[githubworkflow-url]:https://github.com/sasjs/core/blob/main/.github/workflows/main.yml
[dependency-image]:https://david-dm.org/sasjs/core.svg
[dependency-url]:https://github.com/sasjs/core/blob/main/package.json
Much quality. Many standards. The **Macro Core** library exists to save time and development effort! Herein ye shall find a veritable host of MIT-licenced, production quality SAS macros. These are a mix of tools, utilities, functions and code generators that are useful in the context of [Application Development](https://sasapps.io) on the SAS platform (eg https://datacontroller.io). [Contributions](https://github.com/sasjs/core/blob/main/CONTRIBUTING.md) are welcomed.
You can download and compile them all in just two lines of SAS code:
@@ -32,49 +29,61 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
Documentation: https://core.sasjs.io
# Components
## Components
**base** library (SAS9/Viya)
### **base** library (SAS9/Viya)
- OS independent
- Not metadata aware
- No X command
- Prefixes: _mf_, _mp_
**fcmp** library (SAS9/Viya)
#### **fcmp** library (SAS9/Viya)
- Function and macro names are identical, except for special cases
- Prefixes: _mcf_
The fcmp macros are used to generate fcmp functions, and can be used with or
without the `proc fcmp` wrapper.
**meta** library (SAS9 only)
### **meta** library (SAS9 only)
Macros used in SAS EBI, which connect to the metadata server.
- OS independent
- Metadata aware
- No X command
- Prefixes: _mm_
**viya** library (Viya only)
### **server** library (@sasjs/server only)
These macros are used for building applications using [@sasjs/server](https://server.sasjs.io) - an open source REST API for Desktop SAS.
- OS independent
- @sasjs/server aware
- No X command
- Prefixes: _ms_
### **viya** library (Viya only)
Macros used for interfacing with SAS Viya.
- OS independent
- No X command
- Prefixes: _mv_
- Prefixes: _mv_, _mvf_
**metax** library (SAS9 only)
### **metax** library (SAS9 only)
- OS specific
- Metadata aware
- X command enabled
- Prefixes: _mmw_,_mmu_,_mmx_
**lua** library
### **lua** library
Wait - this is a macro library - what is LUA doing here? Well, it is a little known fact that you CAN run LUA within a SAS Macro. It has to be written to a text file with a `.lua` extension, from where you can `%include` it. So, without using the `proc lua` wrapper.
To contribute, simply write your freeform LUA in the LUA folder. Then run the `build.py`, which will convert your LUA into a data step with put statements, and create the macro wrapper with a `ml_` prefix. You can then use your module in any program by running:
```
```sas
/* compile the lua module */
%ml_yourmodule()
@@ -89,7 +98,7 @@ run;
- X command enabled
- Prefixes: _mmw_,_mmu_,_mmx_
# Installation
## Installation
First, download the repo to a location your SAS system can access. Then update your sasautos path to include the components you wish to have available, eg:
@@ -107,9 +116,9 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
```
# Standards
## Standards
## File Properties
### File Properties
- filenames much match macro names
- filenames must be lowercase, without spaces
@@ -117,19 +126,20 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
- one macro per file
- prefixes:
- _mf_ for macro functions (can be used in open code).
- _mp_ for macro procedures (which generate sas code)
- _ml_ for macros that are used to compile LUA modules
- _mm_ for metadata macros (interface with the metadata server).
- _mmx_ for macros that use metadata and are XCMD enabled
- _mp_ for macro procedures (which generate sas code)
- _ms_ for macro procedures that will only work with [@sasjs/server](https://github.com/sasjs/server)
- _mv_ for macro procedures that will only work in Viya
- _mx_ for macros that are XCMD enabled
- _ml_ for macros that are used to compile LUA modules
- _mv_ for macros that will only work in Viya
- follow verb-noun convention
- unix style line endings (lf)
- individual lines should be no more than 80 characters long
- UTF-8
## Header Properties
### Header Properties
The **Macro Core** documentation is created using [doxygen](http://www.doxygen.nl). A full list of attributes can be found [here](http://www.doxygen.nl/manual/commands.html) but the following are most relevant:
@@ -143,7 +153,7 @@ The **Macro Core** documentation is created using [doxygen](http://www.doxygen.n
All macros must be commented in the doxygen format, to enable the [online documentation](https://core.sasjs.io).
### Dependencies
#### Dependencies
SAS code can contain one of two types of dependency - SAS Macros, and SAS Includes. When compiling projects using the [SASjs CLI](https://cli.sasjs.io) the doxygen header is scanned for ` @li` items under the following headers:
```sas
@@ -161,7 +171,7 @@ The CLI can then extract all the dependencies and insert as precode (SAS Macros)
When contributing to this library, it is therefore important to ensure that all dependencies are listed in the header in this format.
## Coding Standards
### Coding Standards
- Indentation = 2 spaces. No tabs!
- no trailing white space
@@ -174,7 +184,7 @@ When contributing to this library, it is therefore important to ensure that all
- Avoid naming collisions! All macro variables should be local scope. Use system generated work tables where possible - eg `data ; set sashelp.class; run; data &output; set &syslast; run;`
- The use of `quit;` for `proc sql` is optional unless you are looking to benefit from the timing statistics.
# General Notes
## General Notes
- All macros should be compatible with SAS versions from support level B and above (so currently 9.2 and later). If an earlier version is not supported, then the macro should say as such in the header documentation, and exit gracefully (eg `%if %sysevalf(&sysver<9.3) %then %return`).
@@ -186,10 +196,9 @@ 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 -->
[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -209,6 +218,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<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>

652
all.sas
View File

@@ -2723,6 +2723,84 @@ run;
run;
%mend mp_cleancsv;
/** @endcond *//**
@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,&copydir)
@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;
if _n_ = 1 then dpos+sum(length(directory),2);
filepath2="&target/"!!substr(filepath,dpos);
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;/**
@file mp_createconstraints.sas
@brief Creates constraints
@details Takes the output from mp_getconstraints.sas as input
@@ -3066,24 +3144,85 @@ data &outds;
run;
%mend mp_deleteconstraints;/**
@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;/**
@file
@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)
@@ -3097,11 +3236,19 @@ run;
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:
@@ -3111,8 +3258,15 @@ run;
- 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
**/
@@ -3121,14 +3275,27 @@ run;
, 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;
@@ -3186,8 +3353,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;
@@ -3228,12 +3395,38 @@ 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;/**
@file
@brief Creates a dataset containing distinct _formatted_ values
@@ -6204,6 +6397,357 @@ select distinct lowcase(memname)
%end;
%mend mp_lib2inserts;/**
@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;
/**
@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;/**
@file
@brief Create a Markdown Table from a dataset
@details A markdown table is a simple table representation for use in
@@ -7567,10 +8111,10 @@ run;
@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;
@@ -7581,8 +8125,9 @@ run;
@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
@@ -7600,7 +8145,8 @@ run;
%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);
@@ -7788,6 +8334,42 @@ alter table &libds modify &var char(&len);
%mend mp_validatecol;
/**
@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;/**
@file
@brief Fix the `_WEBIN` variables provided to SAS web services
@details When uploading files to SAS Stored Processes or Viya Jobs a number
@@ -7863,11 +8445,18 @@ alter table &libds modify &var char(&len);
@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
@@ -7898,9 +8487,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;
@@ -7911,11 +8500,10 @@ ods package open nopf;
data _null_;
set &in;
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;

79
base/mp_copyfolder.sas Normal file
View File

@@ -0,0 +1,79 @@
/**
@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,&copydir)
@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;
if _n_ = 1 then dpos+sum(length(directory),2);
filepath2="&target/"!!substr(filepath,dpos);
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
View 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;

View File

@@ -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;

255
base/mp_lockanytable.sas Normal file
View 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
View 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;

View File

@@ -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 &in;
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;

View File

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

View File

@@ -55,7 +55,18 @@
*/
/*! \dir Tests
/*! \dir server
* \brief Macros used with [sasjs/server](https://server.sasjs.io)
* \details These macros have the following attributes:
* OS independent
* sasjs/server aware
* No X command
* Prefixes: _ms_
*/
/*! \dir tests
* \brief SASjs Tests
* \details These folders contain the macro tests. They are first compiled
and deployed (sasjs cbd) then executed (sasjs test).
@@ -72,7 +83,7 @@
*/
/*! \dir lua
/*! \dir lua
* \brief Lua macros
* \details These macros have the following attributes:

View File

@@ -1,29 +0,0 @@
#!/usr/bin/env bash
# Concatenate all macros into a single file
OUTFILE='./macrocore.sas'
cat > $OUTFILE <<'EOL'
/**
@file
@brief Auto-generated file
@details
This file contains all the macros in a single file - which means it can be
'included' in SAS with just 2 lines of code:
filename mc url
"https://raw.githubusercontent.com/sasjs/core/main/macrocore.sas";
%inc mc;
The `build.sh` file in the https://github.com/sasjs/core repo
is used to create this file.
@author Allan Bowe
**/
EOL
cat base/* >> $OUTFILE
cat meta/* >> $OUTFILE
cat metax/* >> $OUTFILE
cat viya/* >> $OUTFILE

View File

@@ -5,6 +5,7 @@
"fcmp",
"meta",
"metax",
"server",
"viya",
"lua",
"tests/crossplatform"
@@ -55,6 +56,18 @@
"deployServicePack": true
}
},
{
"name": "server",
"serverUrl": "https://sas.analytium.co.uk:5001",
"serverType": "SASJS",
"appLoc": "/Shared Data/temp/macrocore",
"macroFolders": [
"tests/serveronly"
],
"deployConfig": {
"deployServicePack": true
}
},
{
"name": "docsonly",
"serverType": "SAS9",

170
server/ms_webout.sas Normal file
View File

@@ -0,0 +1,170 @@
/**
@file
@brief Send data to/from @sasjs/server
@details This macro should be added to the start of each web service,
**immediately** followed by a call to:
%ms_webout(FETCH)
This will read all the input data and create same-named SAS datasets in the
WORK library. You can then insert your code, and send data back using the
following syntax:
data some datasets; * make some data ;
retain some columns;
run;
%ms_webout(OPEN)
%ms_webout(ARR,some) * Array format, fast, suitable for large tables ;
%ms_webout(OBJ,datasets) * Object format, easier to work with ;
%ms_webout(CLOSE)
@param action Either FETCH, OPEN, ARR, OBJ or CLOSE
@param ds The dataset to send back to the frontend
@param dslabel= value to use instead of the real name for sending to JSON
@param fmt=(Y) Set to N to send back unformatted values
@param fref=(_webout) The fileref to which to write the JSON
<h4> SAS Macros </h4>
@li mp_jsonout.sas
@li mf_getuser.sas
<h4> Related Macros </h4>
@li mv_webout.sas
@li mm_webout.sas
@version 9.3
@author Allan Bowe
**/
%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=Y);
%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug
sasjs_tables;
%local i tempds;
%let action=%upcase(&action);
%if &action=FETCH %then %do;
%if %str(&_debug) ge 131 %then %do;
options mprint notes mprintnest;
%end;
%let _webin_file_count=%eval(&_webin_file_count+0);
/* now read in the data */
%do i=1 %to &_webin_file_count;
%if &_webin_file_count=1 %then %do;
%let _webin_fileref1=&_webin_fileref;
%let _webin_name1=&_webin_name;
%end;
data _null_;
infile &&_webin_fileref&i termstr=crlf;
input;
call symputx('input_statement',_infile_);
putlog "&&_webin_name&i input statement: " _infile_;
stop;
data &&_webin_name&i;
infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding='utf-8';
input &input_statement;
%if %str(&_debug) ge 131 %then %do;
if _n_<20 then putlog _infile_;
%end;
run;
%let sasjs_tables=&sasjs_tables &&_webin_name&i;
%end;
%end;
%else %if &action=OPEN %then %do;
/* fix encoding */
OPTIONS NOBOMFILE;
/* setup json */
data _null_;file &fref encoding='utf-8';
%if %str(&_debug) ge 131 %then %do;
put '>>weboutBEGIN<<';
%end;
put '{"SYSDATE" : "' "&SYSDATE" '"';
put ',"SYSTIME" : "' "&SYSTIME" '"';
run;
%end;
%else %if &action=ARR or &action=OBJ %then %do;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref
,engine=&jsonengine,dbg=%str(&_debug)
)
%end;
%else %if &action=CLOSE %then %do;
%if %str(&_debug) ge 131 %then %do;
/* if debug mode, send back first 10 records of each work table also */
options obs=10;
data;run;%let tempds=%scan(&syslast,2,.);
ods output Members=&tempds;
proc datasets library=WORK memtype=data;
%local wtcnt;%let wtcnt=0;
data _null_;
set &tempds;
if not (upcase(name) =:"DATA"); /* ignore temp datasets */
i+1;
call symputx('wt'!!left(i),name,'l');
call symputx('wtcnt',i,'l');
data _null_; file &fref mod encoding='utf-8';
put ",""WORK"":{";
%do i=1 %to &wtcnt;
%let wt=&&wt&i;
proc contents noprint data=&wt
out=_data_ (keep=name type length format:);
run;%let tempds=%scan(&syslast,2,.);
data _null_; file &fref mod encoding='utf-8';
dsid=open("WORK.&wt",'is');
nlobs=attrn(dsid,'NLOBS');
nvars=attrn(dsid,'NVARS');
rc=close(dsid);
if &i>1 then put ','@;
put " ""&wt"" : {";
put '"nlobs":' nlobs;
put ',"nvars":' nvars;
%mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=&jsonengine)
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=&jsonengine)
data _null_; file &fref mod encoding='utf-8';
put "}";
%end;
data _null_; file &fref mod encoding='utf-8';
put "}";
run;
%end;
/* close off json */
data _null_;file &fref mod encoding='utf-8';
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ",""SYSUSERID"" : ""&sysuserid"" ";
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
put ",""_DEBUG"" : ""&_debug"" ";
put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
SYSHOSTINFOLONG=quote(trim(symget('SYSHOSTINFOLONG')));
put ',"SYSHOSTINFOLONG" : ' SYSHOSTINFOLONG;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";
put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";
put ",""SYSPROCESSNAME"" : ""&SYSPROCESSNAME"" ";
put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""SYSSCPL"" : ""&sysscpl"" ";
put ",""SYSSITE"" : ""&syssite"" ";
put ",""SYSTCPIPHOSTNAME"" : ""&SYSTCPIPHOSTNAME"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" ';
autoexec=quote(trim(getoption('autoexec')));
put ',"AUTOEXEC" : ' autoexec;
memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";
put ',"MEMSIZE" : ' memsize;
put "}" @;
%if %str(&_debug) ge 131 %then %do;
put '>>weboutEND<<';
%end;
run;
%end;
%mend ms_webout;

View 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
)

View 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
)

View 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
)

View 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
)

View 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
)

View 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
)

View File

@@ -0,0 +1,35 @@
/**
@file
@brief Testing ms_webout macro
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li ms_webout.sas
@li mp_assert.sas
**/
%let fref=%mf_getuniquefileref();
%global _metaperson;
data some datasets;
x=1;
run;
%ms_webout(OPEN,fref=&fref)
%ms_webout(ARR,some,fref=&fref)
%ms_webout(OBJ,datasets,fref=&fref)
%ms_webout(CLOSE,fref=&fref)
libname test JSON (&fref);
data root;
set test.root;
call symputx('checkval',sysvlong);
run;
data alldata;
set test.alldata;
run;
%mp_assert(
iftrue=(%str(&checkval)=%str(&sysvlong)),
desc=Check if the sysvlong value was created
)

View File

@@ -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()