mirror of
https://github.com/sasjs/core.git
synced 2026-01-06 00:50:05 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d0754d705 | ||
|
|
80acecd3e6 | ||
|
|
cb2a8db087 | ||
|
|
914dc51aca | ||
|
|
7ce480db6a | ||
|
|
3120ba68ad | ||
|
|
9eff1e0e83 | ||
|
|
678250ba27 | ||
|
|
6845a63196 | ||
|
|
3103abe3c8 | ||
|
|
318fd1ddde | ||
|
|
7b2b81a501 | ||
|
|
02de4e42bf | ||
|
|
ddd831fe7a | ||
|
|
42a46b32e9 | ||
|
|
3b395b3ae5 | ||
|
|
9e84e47563 | ||
|
|
aff29427e2 | ||
|
|
474f1b3cc6 | ||
|
|
22bf8065bb | ||
|
|
7bb089e269 | ||
|
|
a6e9158814 | ||
|
|
19a1336720 | ||
|
|
79d5d42e6b | ||
|
|
2d81de5841 | ||
|
|
107ab836d6 | ||
|
|
5225e74465 | ||
|
|
39253d2828 | ||
|
|
171c169537 | ||
|
|
76af9fa33c | ||
|
|
37ccc8a2aa |
@@ -108,6 +108,15 @@
|
|||||||
"test",
|
"test",
|
||||||
"review"
|
"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,
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|
||||||
|
|
||||||
0
CHANGELOG.md → .github/CHANGELOG.md
vendored
0
CHANGELOG.md → .github/CHANGELOG.md
vendored
0
SECURITY.md → .github/SECURITY.md
vendored
0
SECURITY.md → .github/SECURITY.md
vendored
58
README.md
58
README.md
@@ -1,7 +1,6 @@
|
|||||||
# Macro Core
|
# Macro Core
|
||||||
[![npm package][npm-image]][npm-url]
|
[![npm package][npm-image]][npm-url]
|
||||||
[![Github Workflow][githubworkflow-image]][githubworkflow-url]
|
[![Github Workflow][githubworkflow-image]][githubworkflow-url]
|
||||||
[![Dependency Status][dependency-image]][dependency-url]
|
|
||||||
[]()
|
[]()
|
||||||

|

|
||||||
[](/LICENSE)
|
[](/LICENSE)
|
||||||
@@ -16,11 +15,9 @@
|
|||||||
[npm-url]:http://npmjs.org/package/@sasjs/core
|
[npm-url]:http://npmjs.org/package/@sasjs/core
|
||||||
[githubworkflow-image]:https://github.com/sasjs/core/actions/workflows/main.yml/badge.svg
|
[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
|
[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
|
[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.
|
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:
|
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
|
Documentation: https://core.sasjs.io
|
||||||
|
|
||||||
# Components
|
## Components
|
||||||
|
|
||||||
**base** library (SAS9/Viya)
|
### **base** library (SAS9/Viya)
|
||||||
|
|
||||||
- OS independent
|
- OS independent
|
||||||
- Not metadata aware
|
- Not metadata aware
|
||||||
- No X command
|
- No X command
|
||||||
- Prefixes: _mf_, _mp_
|
- Prefixes: _mf_, _mp_
|
||||||
|
|
||||||
**fcmp** library (SAS9/Viya)
|
#### **fcmp** library (SAS9/Viya)
|
||||||
- Function and macro names are identical, except for special cases
|
- Function and macro names are identical, except for special cases
|
||||||
- Prefixes: _mcf_
|
- Prefixes: _mcf_
|
||||||
|
|
||||||
The fcmp macros are used to generate fcmp functions, and can be used with or
|
The fcmp macros are used to generate fcmp functions, and can be used with or
|
||||||
without the `proc fcmp` wrapper.
|
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
|
- OS independent
|
||||||
- Metadata aware
|
- Metadata aware
|
||||||
- No X command
|
- No X command
|
||||||
- Prefixes: _mm_
|
- 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
|
- OS independent
|
||||||
- No X command
|
- No X command
|
||||||
- Prefixes: _mv_
|
- Prefixes: _mv_, _mvf_
|
||||||
|
|
||||||
**metax** library (SAS9 only)
|
### **metax** library (SAS9 only)
|
||||||
|
|
||||||
- OS specific
|
- OS specific
|
||||||
- Metadata aware
|
- Metadata aware
|
||||||
- X command enabled
|
- X command enabled
|
||||||
- Prefixes: _mmw_,_mmu_,_mmx_
|
- 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.
|
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:
|
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 */
|
/* compile the lua module */
|
||||||
%ml_yourmodule()
|
%ml_yourmodule()
|
||||||
|
|
||||||
@@ -89,7 +98,7 @@ run;
|
|||||||
- X command enabled
|
- X command enabled
|
||||||
- Prefixes: _mmw_,_mmu_,_mmx_
|
- 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:
|
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;
|
%inc mc;
|
||||||
```
|
```
|
||||||
|
|
||||||
# Standards
|
## Standards
|
||||||
|
|
||||||
## File Properties
|
### File Properties
|
||||||
|
|
||||||
- filenames much match macro names
|
- filenames much match macro names
|
||||||
- filenames must be lowercase, without spaces
|
- 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
|
- one macro per file
|
||||||
- prefixes:
|
- prefixes:
|
||||||
- _mf_ for macro functions (can be used in open code).
|
- _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).
|
- _mm_ for metadata macros (interface with the metadata server).
|
||||||
- _mmx_ for macros that use metadata and are XCMD enabled
|
- _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
|
- _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
|
- follow verb-noun convention
|
||||||
- unix style line endings (lf)
|
- unix style line endings (lf)
|
||||||
- individual lines should be no more than 80 characters long
|
- individual lines should be no more than 80 characters long
|
||||||
- UTF-8
|
- 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:
|
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).
|
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 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
|
```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.
|
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!
|
- Indentation = 2 spaces. No tabs!
|
||||||
- no trailing white space
|
- 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;`
|
- 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.
|
- 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`).
|
- 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 ✨
|
## Contributors ✨
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
[](#contributors-)
|
[](#contributors-)
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
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>
|
<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/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/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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
828
all.sas
828
all.sas
@@ -2723,6 +2723,84 @@ run;
|
|||||||
run;
|
run;
|
||||||
%mend mp_cleancsv;
|
%mend mp_cleancsv;
|
||||||
/** @endcond *//**
|
/** @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,©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;
|
||||||
|
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
|
@file mp_createconstraints.sas
|
||||||
@brief Creates constraints
|
@brief Creates constraints
|
||||||
@details Takes the output from mp_getconstraints.sas as input
|
@details Takes the output from mp_getconstraints.sas as input
|
||||||
@@ -3066,24 +3144,85 @@ data &outds;
|
|||||||
run;
|
run;
|
||||||
|
|
||||||
%mend mp_deleteconstraints;/**
|
%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
|
@file
|
||||||
@brief Returns all files and subdirectories within a specified parent
|
@brief Returns all files and subdirectories within a specified parent
|
||||||
@details When used with getattrs=NO, is not OS specific (uses dopen / dread).
|
@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:
|
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
|
https://communities.sas.com/t5/SAS-Programming/SAS-Function-to-convert-string-to-Legal-SAS-Name/m-p/27375/highlight/true#M5003
|
||||||
|
|
||||||
|
|
||||||
usage:
|
usage:
|
||||||
|
|
||||||
%mp_dirlist(path=/some/location,outds=myTable)
|
%mp_dirlist(path=/some/location, outds=myTable, maxdepth=MAX)
|
||||||
|
|
||||||
%mp_dirlist(outds=cwdfileprops, getattrs=YES)
|
%mp_dirlist(outds=cwdfileprops, getattrs=YES)
|
||||||
|
|
||||||
@@ -3097,11 +3236,19 @@ run;
|
|||||||
X CMD) do please raise an issue!
|
X CMD) do please raise an issue!
|
||||||
|
|
||||||
|
|
||||||
@param path= for which to return contents
|
@param [in] path= for which to return contents
|
||||||
@param fref= Provide a DISK engine fileref as an alternative to PATH
|
@param [in] fref= Provide a DISK engine fileref as an alternative to PATH
|
||||||
@param outds= the output dataset to create
|
@param [in] maxdepth= (0) Set to a positive integer to indicate the level of
|
||||||
@param getattrs= YES/NO (default=NO). Uses doptname and foptname to return
|
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
|
||||||
all attributes for each file / folder.
|
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:
|
@returns outds contains the following variables:
|
||||||
@@ -3111,8 +3258,15 @@ run;
|
|||||||
- filename (just the file name)
|
- filename (just the file name)
|
||||||
- ext (.extension)
|
- ext (.extension)
|
||||||
- msg (system message if any issues)
|
- msg (system message if any issues)
|
||||||
|
- level (depth of folder)
|
||||||
- OS SPECIFIC variables, if <code>getattrs=</code> is used.
|
- 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
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
@@ -3121,14 +3275,27 @@ run;
|
|||||||
, fref=0
|
, fref=0
|
||||||
, outds=work.mp_dirlist
|
, outds=work.mp_dirlist
|
||||||
, getattrs=NO
|
, getattrs=NO
|
||||||
|
, maxdepth=0
|
||||||
|
, level=0 /* The level of recursion to perform. For internal use only. */
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%let getattrs=%upcase(&getattrs)XX;
|
%let getattrs=%upcase(&getattrs)XX;
|
||||||
|
|
||||||
data &outds(compress=no
|
/* temp table */
|
||||||
keep=file_or_folder filepath filename ext msg directory
|
%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
|
length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80
|
||||||
ext $20 msg $200;
|
ext $20 msg $200;
|
||||||
|
retain level &level;
|
||||||
%if &fref=0 %then %do;
|
%if &fref=0 %then %do;
|
||||||
rc = filename(fref, "&path");
|
rc = filename(fref, "&path");
|
||||||
%end;
|
%end;
|
||||||
@@ -3186,8 +3353,8 @@ data &outds(compress=no
|
|||||||
run;
|
run;
|
||||||
|
|
||||||
%if %substr(&getattrs,1,1)=Y %then %do;
|
%if %substr(&getattrs,1,1)=Y %then %do;
|
||||||
data &outds;
|
data &out_ds;
|
||||||
set &outds;
|
set &out_ds;
|
||||||
length infoname infoval $60 fref $8;
|
length infoname infoval $60 fref $8;
|
||||||
rc=filename(fref,filepath);
|
rc=filename(fref,filepath);
|
||||||
drop rc infoname fid i close fref;
|
drop rc infoname fid i close fref;
|
||||||
@@ -3228,12 +3395,38 @@ run;
|
|||||||
run;
|
run;
|
||||||
proc sort;
|
proc sort;
|
||||||
by filepath sasname;
|
by filepath sasname;
|
||||||
proc transpose data=&outds out=&outds(drop=_:);
|
proc transpose data=&out_ds out=&out_ds(drop=_:);
|
||||||
id sasname;
|
id sasname;
|
||||||
var infoval;
|
var infoval;
|
||||||
by filepath file_or_folder filename ext ;
|
by filepath file_or_folder filename ext ;
|
||||||
run;
|
run;
|
||||||
%end;
|
%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;/**
|
%mend mp_dirlist;/**
|
||||||
@file
|
@file
|
||||||
@brief Creates a dataset containing distinct _formatted_ values
|
@brief Creates a dataset containing distinct _formatted_ values
|
||||||
@@ -6204,6 +6397,357 @@ select distinct lowcase(memname)
|
|||||||
%end;
|
%end;
|
||||||
|
|
||||||
%mend mp_lib2inserts;/**
|
%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
|
@file
|
||||||
@brief Create a Markdown Table from a dataset
|
@brief Create a Markdown Table from a dataset
|
||||||
@details A markdown table is a simple table representation for use in
|
@details A markdown table is a simple table representation for use in
|
||||||
@@ -7567,10 +8111,10 @@ run;
|
|||||||
@file mp_unzip.sas
|
@file mp_unzip.sas
|
||||||
@brief Unzips a zip file
|
@brief Unzips a zip file
|
||||||
@details Opens the zip file and copies all the contents to another directory.
|
@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
|
It is not possible to retain permissions / timestamps, also the BOF marker
|
||||||
is lost so it cannot extract binary files.
|
is lost so it cannot extract binary files.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||||
%inc mc;
|
%inc mc;
|
||||||
@@ -7581,8 +8125,9 @@ run;
|
|||||||
@li mf_mkdir.sas
|
@li mf_mkdir.sas
|
||||||
@li mf_getuniquefileref.sas
|
@li mf_getuniquefileref.sas
|
||||||
|
|
||||||
@param ziploc= fileref or quoted full path to zip file ("/path/to/file.zip")
|
@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 outdir= (%sysfunc(pathname(work))) Directory in which to write the
|
||||||
|
outputs (created if non existant)
|
||||||
|
|
||||||
@version 9.4
|
@version 9.4
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@@ -7600,7 +8145,8 @@ run;
|
|||||||
%let fname2=%mf_getuniquefileref();
|
%let fname2=%mf_getuniquefileref();
|
||||||
%let fname3=%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 */
|
/* Read the "members" (files) from the ZIP file */
|
||||||
data _data_(keep=memname isFolder);
|
data _data_(keep=memname isFolder);
|
||||||
@@ -7788,6 +8334,42 @@ alter table &libds modify &var char(&len);
|
|||||||
|
|
||||||
%mend mp_validatecol;
|
%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
|
@file
|
||||||
@brief Fix the `_WEBIN` variables provided to SAS web services
|
@brief Fix the `_WEBIN` variables provided to SAS web services
|
||||||
@details When uploading files to SAS Stored Processes or Viya Jobs a number
|
@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
|
@li mp_dirlist.sas
|
||||||
|
|
||||||
@param in= unquoted filepath, dataset of files or directory to zip
|
@param in= unquoted filepath, dataset of files or directory to zip
|
||||||
@param type= FILE, DATASET, DIRECTORY. (FILE / DATASET not ready yet)
|
@param type= (FILE) Valid values:
|
||||||
@param outname= output file to create, without .zip extension
|
@li FILE - /full/path/and/filename.extension to a particular file
|
||||||
@param outpath= location for output zip 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
|
@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
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@source https://github.com/sasjs/core
|
@source https://github.com/sasjs/core
|
||||||
@@ -7898,9 +8487,9 @@ ods package open nopf;
|
|||||||
set &ds;
|
set &ds;
|
||||||
length __command $4000;
|
length __command $4000;
|
||||||
if file_or_folder='file';
|
if file_or_folder='file';
|
||||||
command=cats('ods package add file="',filepath
|
__command=cats('ods package add file="',filepath
|
||||||
,'" mimetype="application/x-compress";');
|
,'" mimetype="application/x-compress";');
|
||||||
call execute(command);
|
call execute(__command);
|
||||||
run;
|
run;
|
||||||
/* tidy up */
|
/* tidy up */
|
||||||
%if &debug=NO %then %do;
|
%if &debug=NO %then %do;
|
||||||
@@ -7911,11 +8500,10 @@ ods package open nopf;
|
|||||||
data _null_;
|
data _null_;
|
||||||
set ∈
|
set ∈
|
||||||
length __command $4000;
|
length __command $4000;
|
||||||
command=cats('ods package add file="',&incol
|
__command=cats('ods package add file="',&incol
|
||||||
,'" mimetype="application/x-compress";');
|
,'" mimetype="application/x-compress";');
|
||||||
call execute(command);
|
call execute(__command);
|
||||||
run;
|
run;
|
||||||
ods package add file="&in" mimetype="application/x-compress";
|
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
|
|
||||||
@@ -13817,6 +14405,177 @@ run;
|
|||||||
%inc &fref1;
|
%inc &fref1;
|
||||||
|
|
||||||
%mend mmx_spkexport;/**
|
%mend mmx_spkexport;/**
|
||||||
|
@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=DATASTEP,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=DATASTEP)
|
||||||
|
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=DATASTEP)
|
||||||
|
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.)";
|
||||||
|
memsize=quote(cats(memsize));
|
||||||
|
put ',"MEMSIZE" : ' memsize;
|
||||||
|
put "}" @;
|
||||||
|
%if %str(&_debug) ge 131 %then %do;
|
||||||
|
put '>>weboutEND<<';
|
||||||
|
%end;
|
||||||
|
run;
|
||||||
|
%end;
|
||||||
|
|
||||||
|
%mend ms_webout;
|
||||||
|
/**
|
||||||
@file
|
@file
|
||||||
@brief Checks whether a file exists in SAS Drive
|
@brief Checks whether a file exists in SAS Drive
|
||||||
@details Returns 1 if the file exists, and 0 if it doesn't. Works by
|
@details Returns 1 if the file exists, and 0 if it doesn't. Works by
|
||||||
@@ -19492,6 +20251,9 @@ run;
|
|||||||
@param [out] pkg= (utils) The output package in which to create the function.
|
@param [out] pkg= (utils) The output package in which to create the function.
|
||||||
Uses a 3 part format: libref.catalog.package
|
Uses a 3 part format: libref.catalog.package
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_existfunction.sas
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mcf_stpsrv_header(wrap=NO
|
%macro mcf_stpsrv_header(wrap=NO
|
||||||
@@ -19501,6 +20263,8 @@ run;
|
|||||||
,pkg=UTILS
|
,pkg=UTILS
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
%if %mf_existfunction(stpsrv_header)=1 %then %return;
|
||||||
|
|
||||||
%if &wrap=YES %then %do;
|
%if &wrap=YES %then %do;
|
||||||
proc fcmp outcat=&lib..&cat..&pkg;
|
proc fcmp outcat=&lib..&cat..&pkg;
|
||||||
%end;
|
%end;
|
||||||
|
|||||||
79
base/mp_copyfolder.sas
Normal file
79
base/mp_copyfolder.sas
Normal 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,©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;
|
||||||
|
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
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
|
@brief Returns all files and subdirectories within a specified parent
|
||||||
@details When used with getattrs=NO, is not OS specific (uses dopen / dread).
|
@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:
|
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
|
https://communities.sas.com/t5/SAS-Programming/SAS-Function-to-convert-string-to-Legal-SAS-Name/m-p/27375/highlight/true#M5003
|
||||||
|
|
||||||
|
|
||||||
usage:
|
usage:
|
||||||
|
|
||||||
%mp_dirlist(path=/some/location,outds=myTable)
|
%mp_dirlist(path=/some/location, outds=myTable, maxdepth=MAX)
|
||||||
|
|
||||||
%mp_dirlist(outds=cwdfileprops, getattrs=YES)
|
%mp_dirlist(outds=cwdfileprops, getattrs=YES)
|
||||||
|
|
||||||
@@ -30,11 +23,19 @@
|
|||||||
X CMD) do please raise an issue!
|
X CMD) do please raise an issue!
|
||||||
|
|
||||||
|
|
||||||
@param path= for which to return contents
|
@param [in] path= for which to return contents
|
||||||
@param fref= Provide a DISK engine fileref as an alternative to PATH
|
@param [in] fref= Provide a DISK engine fileref as an alternative to PATH
|
||||||
@param outds= the output dataset to create
|
@param [in] maxdepth= (0) Set to a positive integer to indicate the level of
|
||||||
@param getattrs= YES/NO (default=NO). Uses doptname and foptname to return
|
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
|
||||||
all attributes for each file / folder.
|
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:
|
@returns outds contains the following variables:
|
||||||
@@ -44,8 +45,15 @@
|
|||||||
- filename (just the file name)
|
- filename (just the file name)
|
||||||
- ext (.extension)
|
- ext (.extension)
|
||||||
- msg (system message if any issues)
|
- msg (system message if any issues)
|
||||||
|
- level (depth of folder)
|
||||||
- OS SPECIFIC variables, if <code>getattrs=</code> is used.
|
- 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
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
@@ -54,14 +62,27 @@
|
|||||||
, fref=0
|
, fref=0
|
||||||
, outds=work.mp_dirlist
|
, outds=work.mp_dirlist
|
||||||
, getattrs=NO
|
, getattrs=NO
|
||||||
|
, maxdepth=0
|
||||||
|
, level=0 /* The level of recursion to perform. For internal use only. */
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%let getattrs=%upcase(&getattrs)XX;
|
%let getattrs=%upcase(&getattrs)XX;
|
||||||
|
|
||||||
data &outds(compress=no
|
/* temp table */
|
||||||
keep=file_or_folder filepath filename ext msg directory
|
%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
|
length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80
|
||||||
ext $20 msg $200;
|
ext $20 msg $200;
|
||||||
|
retain level &level;
|
||||||
%if &fref=0 %then %do;
|
%if &fref=0 %then %do;
|
||||||
rc = filename(fref, "&path");
|
rc = filename(fref, "&path");
|
||||||
%end;
|
%end;
|
||||||
@@ -119,8 +140,8 @@ data &outds(compress=no
|
|||||||
run;
|
run;
|
||||||
|
|
||||||
%if %substr(&getattrs,1,1)=Y %then %do;
|
%if %substr(&getattrs,1,1)=Y %then %do;
|
||||||
data &outds;
|
data &out_ds;
|
||||||
set &outds;
|
set &out_ds;
|
||||||
length infoname infoval $60 fref $8;
|
length infoname infoval $60 fref $8;
|
||||||
rc=filename(fref,filepath);
|
rc=filename(fref,filepath);
|
||||||
drop rc infoname fid i close fref;
|
drop rc infoname fid i close fref;
|
||||||
@@ -161,10 +182,36 @@ run;
|
|||||||
run;
|
run;
|
||||||
proc sort;
|
proc sort;
|
||||||
by filepath sasname;
|
by filepath sasname;
|
||||||
proc transpose data=&outds out=&outds(drop=_:);
|
proc transpose data=&out_ds out=&out_ds(drop=_:);
|
||||||
id sasname;
|
id sasname;
|
||||||
var infoval;
|
var infoval;
|
||||||
by filepath file_or_folder filename ext ;
|
by filepath file_or_folder filename ext ;
|
||||||
run;
|
run;
|
||||||
%end;
|
%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;
|
%mend mp_dirlist;
|
||||||
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;
|
||||||
@@ -16,11 +16,18 @@
|
|||||||
@li mp_dirlist.sas
|
@li mp_dirlist.sas
|
||||||
|
|
||||||
@param in= unquoted filepath, dataset of files or directory to zip
|
@param in= unquoted filepath, dataset of files or directory to zip
|
||||||
@param type= FILE, DATASET, DIRECTORY. (FILE / DATASET not ready yet)
|
@param type= (FILE) Valid values:
|
||||||
@param outname= output file to create, without .zip extension
|
@li FILE - /full/path/and/filename.extension to a particular file
|
||||||
@param outpath= location for output zip 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
|
@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
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@source https://github.com/sasjs/core
|
@source https://github.com/sasjs/core
|
||||||
@@ -51,9 +58,9 @@ ods package open nopf;
|
|||||||
set &ds;
|
set &ds;
|
||||||
length __command $4000;
|
length __command $4000;
|
||||||
if file_or_folder='file';
|
if file_or_folder='file';
|
||||||
command=cats('ods package add file="',filepath
|
__command=cats('ods package add file="',filepath
|
||||||
,'" mimetype="application/x-compress";');
|
,'" mimetype="application/x-compress";');
|
||||||
call execute(command);
|
call execute(__command);
|
||||||
run;
|
run;
|
||||||
/* tidy up */
|
/* tidy up */
|
||||||
%if &debug=NO %then %do;
|
%if &debug=NO %then %do;
|
||||||
@@ -64,11 +71,10 @@ ods package open nopf;
|
|||||||
data _null_;
|
data _null_;
|
||||||
set ∈
|
set ∈
|
||||||
length __command $4000;
|
length __command $4000;
|
||||||
command=cats('ods package add file="',&incol
|
__command=cats('ods package add file="',&incol
|
||||||
,'" mimetype="application/x-compress";');
|
,'" mimetype="application/x-compress";');
|
||||||
call execute(command);
|
call execute(__command);
|
||||||
run;
|
run;
|
||||||
ods package add file="&in" mimetype="application/x-compress";
|
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2
build.py
2
build.py
@@ -84,7 +84,7 @@ options noquotelenmax;
|
|||||||
"""
|
"""
|
||||||
f = open('all.sas', "w") # r / r+ / rb / rb+ / w / wb
|
f = open('all.sas', "w") # r / r+ / rb / rb+ / w / wb
|
||||||
f.write(header)
|
f.write(header)
|
||||||
folders=['base','meta','metax','viya','lua','fcmp']
|
folders=['base','meta','metax','server','viya','lua','fcmp']
|
||||||
for folder in folders:
|
for folder in folders:
|
||||||
filenames = [fn for fn in Path('./' + folder).iterdir() if fn.match("*.sas")]
|
filenames = [fn for fn in Path('./' + folder).iterdir() if fn.match("*.sas")]
|
||||||
filenames.sort()
|
filenames.sort()
|
||||||
|
|||||||
@@ -54,6 +54,9 @@
|
|||||||
@param [out] pkg= (utils) The output package in which to create the function.
|
@param [out] pkg= (utils) The output package in which to create the function.
|
||||||
Uses a 3 part format: libref.catalog.package
|
Uses a 3 part format: libref.catalog.package
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_existfunction.sas
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mcf_stpsrv_header(wrap=NO
|
%macro mcf_stpsrv_header(wrap=NO
|
||||||
@@ -63,6 +66,8 @@
|
|||||||
,pkg=UTILS
|
,pkg=UTILS
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
%if %mf_existfunction(stpsrv_header)=1 %then %return;
|
||||||
|
|
||||||
%if &wrap=YES %then %do;
|
%if &wrap=YES %then %do;
|
||||||
proc fcmp outcat=&lib..&cat..&pkg;
|
proc fcmp outcat=&lib..&cat..&pkg;
|
||||||
%end;
|
%end;
|
||||||
|
|||||||
15
main.dox
15
main.dox
@@ -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
|
* \brief SASjs Tests
|
||||||
* \details These folders contain the macro tests. They are first compiled
|
* \details These folders contain the macro tests. They are first compiled
|
||||||
and deployed (sasjs cbd) then executed (sasjs test).
|
and deployed (sasjs cbd) then executed (sasjs test).
|
||||||
@@ -72,7 +83,7 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \dir lua
|
/*! \dir lua
|
||||||
* \brief Lua macros
|
* \brief Lua macros
|
||||||
* \details These macros have the following attributes:
|
* \details These macros have the following attributes:
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
"fcmp",
|
"fcmp",
|
||||||
"meta",
|
"meta",
|
||||||
"metax",
|
"metax",
|
||||||
|
"server",
|
||||||
"viya",
|
"viya",
|
||||||
"lua",
|
"lua",
|
||||||
"tests/crossplatform"
|
"tests/crossplatform"
|
||||||
@@ -55,6 +56,18 @@
|
|||||||
"deployServicePack": true
|
"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",
|
"name": "docsonly",
|
||||||
"serverType": "SAS9",
|
"serverType": "SAS9",
|
||||||
|
|||||||
171
server/ms_webout.sas
Normal file
171
server/ms_webout.sas
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
/**
|
||||||
|
@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=DATASTEP,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=DATASTEP)
|
||||||
|
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=DATASTEP)
|
||||||
|
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.)";
|
||||||
|
memsize=quote(cats(memsize));
|
||||||
|
put ',"MEMSIZE" : ' memsize;
|
||||||
|
put "}" @;
|
||||||
|
%if %str(&_debug) ge 131 %then %do;
|
||||||
|
put '>>weboutEND<<';
|
||||||
|
%end;
|
||||||
|
run;
|
||||||
|
%end;
|
||||||
|
|
||||||
|
%mend ms_webout;
|
||||||
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
|
||||||
|
)
|
||||||
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
|
||||||
|
)
|
||||||
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
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
35
tests/serveronly/ms_webout.test.sas
Normal file
35
tests/serveronly/ms_webout.test.sas
Normal 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
|
||||||
|
)
|
||||||
@@ -5,4 +5,12 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
/* location in metadata or SAS Drive for temporary files */
|
/* 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()
|
||||||
Reference in New Issue
Block a user