1
0
mirror of https://github.com/sasjs/core.git synced 2025-12-17 00:54:36 +00:00

Compare commits

...

67 Commits

Author SHA1 Message Date
Allan Bowe
a7fdb52231 fix: sasmeta vs basesas results 2021-04-05 18:26:31 +00:00
Allan Bowe
066ed00e44 chore: reducing line length in lint by 5 characters 2021-04-05 13:31:09 +00:00
Allan Bowe
49fbc210ad Update .gitpod.yml 2021-04-05 14:22:58 +01:00
Allan Bowe
951aa474f2 Update .gitpod.dockerfile 2021-04-05 14:16:22 +01:00
Allan Bowe
961dd54ee0 Update .gitpod.dockerfile 2021-04-05 13:29:39 +01:00
Allan Bowe
921354dac7 Update .gitpod.yml 2021-04-05 12:59:31 +01:00
Allan Bowe
48212f8797 Merge pull request #11 from sasjs/sasjslintfixes
sasjs lint fixes
2021-04-03 20:58:58 +01:00
cb8992dade fix: remove .githooks now we have sasjs lint 2021-04-03 21:54:50 +02:00
7dec3120be chore: dependencies 2021-04-03 21:35:44 +02:00
9568b17f20 feat: enabling sasjs lint as a git pre-commit hook when contributing to @sasjs/core. To use, just run
> ghooks@2.0.4 install /home/zah/git/core/node_modules/ghooks
> node ./bin/module-install

> @sasjs/core@1.0.0 postinstall /home/zah/git/core
> node-git-hooks

Installing Git hooks...
added 14 packages from 12 contributors and audited 205 packages in 4.23s

17 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities from the repository.
2021-04-03 21:34:40 +02:00
0a38056c69 fix: linting 2021-04-03 21:30:51 +02:00
Allan Bowe
096bf4fa11 chore: more indentation fixes 2021-04-03 18:21:29 +00:00
Allan Bowe
030c4a4fc1 fix: .sasjslint file and indentation issues 2021-04-03 18:10:41 +00:00
1b70205cab feat: two new macros, mp_mdtablewrite.sas (will create a markdown table from a sas dataset) and mm_getlibmetadiffs.sas (will create a set of datasets to describe the differences between a library in metadata and the physical library) 2021-04-02 13:38:46 +02:00
539447ed06 chore: fixing all.sas 2021-03-30 16:34:11 +02:00
e3c333ea39 feat: adding mm_deletelibrary.sas, a macro to easily delete a SAS Library from metadata. Documentation: https://core.sasjs.io/mm__deletelibrary_8sas.html 2021-03-30 00:02:20 +02:00
ae72446f85 chore: updating all.sas 2021-03-30 00:01:43 +02:00
2b6bf4bd02 chore: doxy fixes 2021-03-30 00:01:08 +02:00
6dbb3760e0 Merge branch 'main' of github.com:sasjs/core into main 2021-03-29 23:02:33 +02:00
200d9a5761 chore: doxy updates 2021-03-29 23:02:17 +02:00
Allan Bowe
01a9a5b823 Update README.md 2021-03-25 20:09:24 +01:00
Allan Bowe
35eadd0e9d Update README.md 2021-03-25 15:45:28 +01:00
5cdca95216 chore: vscode properties 2021-03-24 19:38:02 +01:00
Allan Bowe
81b75a32ed chore: latest sasjs version 2021-03-24 18:34:58 +00:00
b7f5a2ec00 chore: devops rules, updated gitignore and git hook 2021-03-22 10:14:52 +01:00
db859bbf1d chore: adding a git hook to prevent sas files from appearing with capital letters. To install, just run
> @sasjs/core@1.0.0 postinstall
> node-git-hooks

Installing Git hooks...

up to date, audited 2 packages in 1s

found 0 vulnerabilities.
2021-03-21 23:59:21 +01:00
27b56e8efd set executable 2021-03-21 23:33:19 +01:00
28ea218d02 chore: adding pre-commit hook 2021-03-21 22:24:46 +01:00
f356e1f351 chore: updating CONTRIBUTING, preventing files with spaces being added, adding git hooks package 2021-03-21 22:23:50 +01:00
4b375e0b8c fix: leading zero in time component of mf_uid() 2021-03-19 20:34:16 +01:00
7db207dd1c chore: updating all.sas 2021-03-19 20:29:35 +01:00
Allan Bowe
ffdfc57aa6 Merge pull request #9 from tmoody/main
fix: Closes issue #8. Optionally raise SYSCC and exit early on job no…
2021-03-17 17:32:35 +01:00
Trevor Moody
6fc8408988 fix: Syntax correction for raise_err parameter 2021-03-17 16:31:47 +00:00
Trevor Moody
eeb25fa5bc fix: Closes issue #8. Optionally raise SYSCC and exit early on job not completing successfully 2021-03-17 16:27:10 +00:00
Allan Bowe
521d128afe chore: gitpod file 2021-03-17 09:21:16 +00:00
Allan Bowe
0135dd6c8f chore: updating gitpod files 2021-03-17 07:40:48 +00:00
Allan Bowe
1b66c59dc0 feat: adding gitpod.yml with sasjs code extension 2021-03-15 09:21:27 +00:00
96be5c65dc fix: incorrect assignment of 0D as LF and 0A as CR, fixed in both mv_createwebservice.sas and mv_createjob.sas 2021-03-08 22:06:57 +01:00
8f6ef569e1 fix: adding filename clear statements 2021-03-07 18:47:41 +01:00
ff45c5a8b8 fix: ensuring unique filerefs in mm_updatestpsourcecode macro. Marking previous placeholders as deprecated for future release. 2021-03-07 18:46:32 +01:00
fb5f1c820a chore: delisting the sasjs/cli dependency 2021-03-07 16:43:46 +01:00
c0e33175cf feat: mm_getfoldercontents.sas to fetch immediate children for any folder including a root level folder 2021-03-07 16:19:25 +01:00
2bfa72f48f feat: mv_createjob macro for creating a viya job 2021-03-07 11:46:21 +01:00
fdc2e8ac8a chore: header section for mp_lib2cards.sas 2021-03-04 14:20:04 +01:00
2a894419ab feat: updating mp_lib2cards to enable a single file to be created with all the tables for a particular library 2021-03-01 17:49:26 +01:00
58bfc7b4aa chore: updating doxy formatting to include folder descriptions, also updating headers for some macros 2021-03-01 17:48:44 +01:00
818c0f5eae fix: lua feature discovery logic fix 2021-02-21 17:15:49 +01:00
dff9e2f387 chore: doxy updates 2021-02-21 17:15:17 +01:00
6c9256e097 chore: adding a CONTRIBUTING.md file 2021-02-12 22:07:04 +01:00
0631a05a78 chore(docs): adding a homepage to the doc site and integrating with the sasjs doc command. See https://core.sasjs.io 2021-02-10 00:12:49 +01:00
268bdca4e0 chore: adding sasjsconfig schema file and updating package.json with SASjs CLI devDependency (used to generate doxygen docs) 2021-02-07 21:47:34 +01:00
Allan Bowe
e38f331ad5 Merge pull request #6 from sasjs/sasjsdoc
initial commit
2021-02-04 15:48:20 +02:00
8d64b30419 fix: adding a one second pause between every SAS Job Request in mv_jobflow.sas 2021-02-04 14:12:02 +01:00
4a6c8ffbb3 fix: replacing WARNING with %str(WARN)ING to avoid being caught in searches for mf_getattrn 2021-01-31 18:34:10 +01:00
b5c86e7031 fix: mv_jobflow param mixup, not all jobs were running (fixed now). Also fixed doc formatting, removed unnecessary logging, and fixed a debug switch. 2021-01-31 17:58:13 +01:00
9783edd0e3 feat: adding outref option to mv_jobflow so that logs of submitted jobs can be captured. Also making the context name and flow id optional in the input table, for ease of use. 2021-01-29 12:56:12 +01:00
961728a987 chore: updating header link 2021-01-27 00:28:26 +01:00
4b34322d94 feat: mv_getjoblog.sas macro - will fetch a SAS log from an executed SAS Viya log and append it to a fileref.
mv_jobwaitfor is updated to allow the log to be fetched for all the submitted jobs.
2021-01-27 00:14:21 +01:00
8bb83deede fix: updating return codes 2021-01-26 16:04:44 +01:00
79c81aa8a4 feat: mf_existfileref macro 2021-01-26 16:00:23 +01:00
bbbcf7d550 chore: updating the docs for mf_getquotedstr 2021-01-23 13:37:15 +02:00
82184bc6be fix: adding quit statement so that exit loop would work on step boundary 2021-01-21 21:55:07 +02:00
efc731cfaa feat: mp_testjob macro for running arbitrary long jobs 2021-01-21 21:48:05 +02:00
da9a74ee14 chore: updating doxy headers 2021-01-21 21:47:41 +02:00
94762d9381 feat: mv_jobflow macro - enables a SAS program to kick off multiple waves of SAS Viya jobs, and to limit those waves by a maximum number of parallel (concurrent) running jobs. 2021-01-16 21:34:17 +02:00
Saad Jutt
fbd8196230 chore: Doxyfile updated + others formatted 2021-01-09 13:42:55 +05:00
Allan Bowe
5720caaf86 initial commit 2021-01-08 14:28:31 +00:00
143 changed files with 6825 additions and 2018 deletions

7
.gitignore vendored
View File

@@ -1,2 +1,9 @@
node_modules node_modules
.DS_Store .DS_Store
sasjsbuild/
# avoid filenames with spaces being committed to source control
**\ **
# ignore the mc_* files - containing macros for individual libraries
mc_*

6
.gitpod.dockerfile Normal file
View File

@@ -0,0 +1,6 @@
FROM gitpod/workspace-full
RUN sudo apt-get update \
&& sudo apt-get install -y \
doxygen \
&& sudo rm -rf /var/lib/apt/lists/*

8
.gitpod.yml Normal file
View File

@@ -0,0 +1,8 @@
tasks:
- init: npm i -g @sasjs/cli
image:
file: .gitpod.dockerfile
vscode:
extensions:
- sasjs.sasjs-for-vscode@1.7.2:R6y1nzpFh2P99BZg5FgH5g==

10
.sasjslint Normal file
View File

@@ -0,0 +1,10 @@
{
"noTrailingSpaces": true,
"noEncodedPasswords": true,
"hasDoxygenHeader": true,
"noSpacesInFileNames": true,
"maxLineLength": 135,
"lowerCaseFileNames": true,
"noTabIndentation": true,
"indentationMultiple": 2
}

9
.vscode/.editorconfig vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"search.exclude": {
"**/sasjsbuild/**": true,
"**/dist/**":true
},
"editor.insertSpaces": true,
"editor.tabSize": 2,
"trim_trailing_whitespace": true
}

10
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,10 @@
{
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.detectIndentation": true,
"editor.formatOnSave": true,
"editor.rulers": [
80
],
"files.trimTrailingWhitespace": true
}

View File

@@ -1,83 +1,31 @@
# Contributing # Contributing
Contributions to the macrocore library are warmly welcomed! To avoid any Contributions are warmly welcomed! To avoid any misunderstandings, do please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before submitting a PR.
misunderstandings, do please first discuss the change you wish to make via issue,
email, or any other method with the owners of this repository before submitting
a PR.
Please note we have a code of conduct, please follow it in all your interactions Please note we have a [code of conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/), please follow it in all your interactions with the project.
with the project.
# Environment Setup
This repository makes use of the [SASjs](https://sasjs.io) framework for code organisation, compilation, documentation, and deployment. The following tools are highly recommended:
* [NPM](https://sasjs.io/windows/#npm) - the runtime and dependency manager for [SASjs CLI](https://cli.sasjs.io) (batteries included)
* [VSCode](https://sasjs.io/windows/#vscode) - feature packed IDE for code editing (warning - highly effective!)
* [GIT](https://sasjs.io/windows/#git) - a safety net you cannot (and should not) do without.
For generating the documentation (`sasjs doc`) it is also necessary to install [doxygen](https://www.doxygen.nl/manual/install.html).
## Code of Conduct To get configured:
### Our Pledge 1. Clone the repository
2. Install local dependencies (`npm install`)
3. Install the SASjs CLI globally (`npm install -g @sasjs/cli`)
4. Add a target, and authentication (`npm add`). See [docs](https://cli.sasjs.io/add/).
In the interest of fostering an open and welcoming environment, we as To contribute:
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
### Our Standards 1. Create your feature branch (`git checkout -b myfeature`)
2. Make your change
3. Update the `all.sas` file (`python3 build.py`)
4. Push and make a PR
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
### Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
### Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
### Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at support@macropeople.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
### Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -1,6 +1,6 @@
# Macro Core # Macro Core
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 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:
@@ -9,7 +9,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc; %inc mc;
``` ```
Documentation: https://sasjs.github.io/core.github.io/files.html Documentation: https://core.sasjs.io
# Components # Components
@@ -84,7 +84,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
## File Properties ## File Properties
- filenames much match macro names - filenames much match macro names
- filenames must be lowercase - filenames must be lowercase, without spaces
- macro names must be lowercase - macro names must be lowercase
- one macro per file - one macro per file
- prefixes: - prefixes:
@@ -99,7 +99,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
- 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
- no trailing white space
## Header Properties ## Header Properties
@@ -118,7 +118,7 @@ All macros must be commented in the doxygen format, to enable the [online docume
### Dependencies ### Dependencies
SAS code can contain one of two types of dependency - SAS Macros, and SAS Programs. 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 Programs. When compiling projects using the [SASjs CLI](https://cli.sasjs.io) the doxygen header is scanned for ` @li` items under the following headers:
``` ```sas
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_nobs.sas @li mf_nobs.sas
@li mm_assignlib.sas @li mm_assignlib.sas
@@ -136,13 +136,15 @@ When contributing to this library, it is therefore important to ensure that all
## Coding Standards ## Coding Standards
- Indentation = 2 spaces. No tabs! - Indentation = 2 spaces. No tabs!
- no trailing white space
- no invisible characters, other than spaces. If invisibles are needed, use hex literals.
- Macro variables should not have the trailing dot (`&var` not `&var.`) unless necessary to prevent incorrect resolution - Macro variables should not have the trailing dot (`&var` not `&var.`) unless necessary to prevent incorrect resolution
- The closing `%mend;` should not contain the macro name. - The closing `%mend;` should **not** contain the macro name.
- All macros should be defined with brackets, even if no variables are needed - ie `%macro x();` not `%macro x;` - All macros should be defined with brackets, even if no variables are needed - ie `%macro x();` not `%macro x;`
- Mandatory parameters should be positional, all optional parameters should be keyword (var=) style. - Mandatory parameters should be positional, all optional parameters should be keyword (var=) style.
- All dataset references must be 2 level (eg `work.blah`, not `blah`). This is to avoid contention when options [DATASTMTCHK](https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000279064.htm)=ALLKEYWORDS is in effect. - All dataset references must be 2 level (eg `work.blah`, not `blah`). This is to avoid contention when options [DATASTMTCHK](https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000279064.htm)=ALLKEYWORDS is in effect.
- 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;`
- If you have a long-running SQL query, the use of a `quit;` statement is recommended in order 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

1948
all.sas

File diff suppressed because it is too large Load Diff

View File

@@ -49,7 +49,10 @@
input; putlog _infile_; input; putlog _infile_;
i=1; i=1;
retain logonce 0; retain logonce 0;
if (_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR") and logonce=0 then do; if (
_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
) and logonce=0
then do;
call symputx('logline',_n_); call symputx('logline',_n_);
logonce+1; logonce+1;
end; end;
@@ -112,7 +115,8 @@
%let syscc=0; %let syscc=0;
%if %symexist(SYS_JES_JOB_URI) %then %do; %if %symexist(SYS_JES_JOB_URI) %then %do;
/* refer web service output to file service in one hit */ /* refer web service output to file service in one hit */
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json"; filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"
name="_webout.json";
%let rc=%sysfunc(fcopy(_web,_webout)); %let rc=%sysfunc(fcopy(_web,_webout));
%end; %end;
%else %do; %else %do;

View File

@@ -32,9 +32,11 @@
%put Supported features: PROCLUA; %put Supported features: PROCLUA;
%end; %end;
%else %if &feature=PROCLUA %then %do; %else %if &feature=PROCLUA %then %do;
/* https://blogs.sas.com/content/sasdummy/2015/08/03/using-lua-within-your-sas-programs */
%if &platform=SASVIYA %then 1; %if &platform=SASVIYA %then 1;
%else %if "&sysver"="9.3" or "&sysver"="9.4" %then 1; %else %if "&sysver"="9.2" or "&sysver"="9.3" %then 0;
%else 0; %else %if "&SYSVLONG" < "9.04.01M3" %then 0;
%else 1;
%end; %end;
%else %do; %else %do;
-1 -1

27
base/mf_existfileref.sas Normal file
View File

@@ -0,0 +1,27 @@
/**
@file
@brief Checks whether a fileref exists
@details You can probably do without this macro as it is just a one liner.
Mainly it is here as a convenient way to remember the syntax!
@param fref the fileref to detect
@return output Returns 1 if found and 0 if not found. Note - it is possible
that the fileref is found, but the file does not (yet) exist. If you need
to test for this, you may as well use the fileref function directly.
@version 8
@author [Allan Bowe](https://www.linkedin.com/in/allanbowe/)
**/
%macro mf_existfileref(fref
)/*/STORE SOURCE*/;
%if %sysfunc(fileref(&fref))=0 %then %do;
1
%end;
%else %do;
0
%end;
%mend;

View File

@@ -23,7 +23,7 @@
%local dsid rc; %local dsid rc;
%let dsid=%sysfunc(open(&libds,is)); %let dsid=%sysfunc(open(&libds,is));
%if &dsid = 0 %then %do; %if &dsid = 0 %then %do;
%put WARNING: Cannot open %trim(&libds), system message below; %put %str(WARN)ING: Cannot open %trim(&libds), system message below;
%put %sysfunc(sysmsg()); %put %sysfunc(sysmsg());
-1 -1
%end; %end;

View File

@@ -32,7 +32,9 @@
/* in case the parameter is a libref.tablename, pull off just the libref */ /* in case the parameter is a libref.tablename, pull off just the libref */
%let libref = %upcase(%scan(&libref, 1, %str(.))); %let libref = %upcase(%scan(&libref, 1, %str(.)));
%let dsid=%sysfunc(open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)); %let dsid=%sysfunc(
open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)
);
%if (&dsid ^= 0) %then %do; %if (&dsid ^= 0) %then %do;
%let engnum=%sysfunc(varnum(&dsid,ENGINE)); %let engnum=%sysfunc(varnum(&dsid,ENGINE));
%let rc=%sysfunc(fetch(&dsid)); %let rc=%sysfunc(fetch(&dsid));

View File

@@ -27,21 +27,23 @@
or "&sysprocessmode"= "SAS Compute Server" %then %do; or "&sysprocessmode"= "SAS Compute Server" %then %do;
SASVIYA SASVIYA
%end; %end;
%else %if "&sysprocessmode"="SAS Stored Process Server" %then %do; %else %if "&sysprocessmode"="SAS Stored Process Server"
or "&sysprocessmode"="SAS Workspace Server"
%then %do;
SASMETA SASMETA
%return; %return;
%end; %end;
%else %do; %else %do;
SAS BASESAS
%return; %return;
%end; %end;
%end; %end;
%else %if %symexist(_metaport) %then %do; %else %if %symexist(_metaport) or %symexist(_metauser) %then %do;
SASMETA SASMETA
%return; %return;
%end; %end;
%else %do; %else %do;
SAS BASESAS
%return; %return;
%end; %end;
%end; %end;

View File

@@ -8,6 +8,13 @@
which returns: which returns:
> 'blah','blah','blah' > 'blah','blah','blah'
Alternatively:
%put %mf_getquotedstr(these words are double quoted,quote=D)
for:
> "these","words","are","double","quoted"
@param in_str the unquoted, spaced delimited string to transform @param in_str the unquoted, spaced delimited string to transform
@param dlm= the delimeter to be applied to the output (default comma) @param dlm= the delimeter to be applied to the output (default comma)
@param indlm= the delimeter used for the input (default is space) @param indlm= the delimeter used for the input (default is space)

View File

@@ -9,7 +9,8 @@
%put mf_isblank(&var); %put mf_isblank(&var);
inspiration: https://support.sas.com/resources/papers/proceedings09/022-2009.pdf inspiration:
https://support.sas.com/resources/papers/proceedings09/022-2009.pdf
@param param VALUE to be checked @param param VALUE to be checked

View File

@@ -6,7 +6,8 @@
%let isdir=%mf_isdir(/tmp); %let isdir=%mf_isdir(/tmp);
With thanks and full credit to Andrea Defronzo - https://www.linkedin.com/in/andrea-defronzo-b1a47460/ With thanks and full credit to Andrea Defronzo -
https://www.linkedin.com/in/andrea-defronzo-b1a47460/
@param path full path of the file/directory to be checked @param path full path of the file/directory to be checked

View File

@@ -1,7 +1,8 @@
/** /**
@file mf_mval.sas @file mf_mval.sas
@brief Returns a macro variable value if the variable exists @brief Returns a macro variable value if the variable exists
@details Use this macro to avoid repetitive use of `%if %symexist(MACVAR) %then` @details
Use this macro to avoid repetitive use of `%if %symexist(MACVAR) %then`
type logic. type logic.
Usage: Usage:

View File

@@ -12,7 +12,8 @@
@param basestr The string to be modified @param basestr The string to be modified
@param trimstr The string to be removed from the end of `basestr`, if it exists @param trimstr The string to be removed from the end of `basestr`, if it
exists
@return output returns result with the value of `trimstr` removed from the end @return output returns result with the value of `trimstr` removed from the end

View File

@@ -1,11 +1,11 @@
/** /**
@file @file
@brief Creates a Unique ID based on system time in a friendly format @brief Creates a unique ID based on system time in friendly format
@details format = YYYYMMDD_HHMMSSmmm_<sysjobid>_<3randomDigits> @details format = YYYYMMDD_HHMMSSmmm_<sysjobid>_<3randomDigits>
%put %mf_uid(); %put %mf_uid();
@version 9.2 @version 9.3
@author Allan Bowe @author Allan Bowe
**/ **/
@@ -14,7 +14,7 @@
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local today now; %local today now;
%let today=%sysfunc(today(),yymmddn8.); %let today=%sysfunc(today(),yymmddn8.);
%let now=%sysfunc(compress(%sysfunc(time(),time12.3),:.)); %let now=%sysfunc(compress(%sysfunc(time(),tod12.3),:.));
&today._&now._&sysjobid._%sysevalf(%sysfunc(ranuni(0))*999,CEIL) &today._&now._&sysjobid._%sysevalf(%sysfunc(ranuni(0))*999,CEIL)

View File

@@ -6,11 +6,11 @@
results back to the client in an STP Web App context, or completely stop results back to the client in an STP Web App context, or completely stop
in the case of a batch run. in the case of a batch run.
Using SAS Abort Cancel mechanisms can cause hung sessions in some Stored Process Using SAS Abort Cancel mechanisms can cause hung sessions in some Stored
environments. This macro takes a unique approach - we set the SAS syscc to 0, Process environments. This macro takes a unique approach - we set the SAS
run `stpsrvset('program error', 0)` (if SAS 9) and then - we open a macro syscc to 0, run `stpsrvset('program error', 0)` (if SAS 9) and then - we open
but don't close it! This provides a graceful abort for SAS web services in all a macro but don't close it! This provides a graceful abort for SAS web
web enabled environments. services in all web enabled environments.
@param mac= to contain the name of the calling macro @param mac= to contain the name of the calling macro
@param msg= message to be returned @param msg= message to be returned
@@ -48,7 +48,10 @@
input; putlog _infile_; input; putlog _infile_;
i=1; i=1;
retain logonce 0; retain logonce 0;
if (_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR") and logonce=0 then do; if (
_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
) and logonce=0 then
do;
call symputx('logline',_n_); call symputx('logline',_n_);
logonce+1; logonce+1;
end; end;

View File

@@ -4,7 +4,8 @@
@details Reads in a file byte by byte and writes it back out. Is an @details Reads in a file byte by byte and writes it back out. Is an
os-independent method to copy files. In case of naming collision, the os-independent method to copy files. In case of naming collision, the
default filerefs can be modified. default filerefs can be modified.
Based on http://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file Based on:
https://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout) %mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)

View File

@@ -23,7 +23,7 @@
%macro mp_cleancsv(in=NOTPROVIDED,out=NOTPROVIDED,qchar='22'x); %macro mp_cleancsv(in=NOTPROVIDED,out=NOTPROVIDED,qchar='22'x);
%if "&in"="NOTPROVIDED" or "&out"="NOTPROVIDED" %then %do; %if "&in"="NOTPROVIDED" or "&out"="NOTPROVIDED" %then %do;
%put %str(ERR)OR: Please provide valid input (&in) and output (&out) locations; %put %str(ERR)OR: Please provide valid input (&in) & output (&out) locations;
%return; %return;
%end; %end;

View File

@@ -57,8 +57,11 @@
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%let getattrs=%upcase(&getattrs)XX; %let getattrs=%upcase(&getattrs)XX;
data &outds (compress=no keep=file_or_folder filepath filename ext msg directory); data &outds(compress=no
length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80 ext $20 msg $200; keep=file_or_folder filepath filename ext msg directory
);
length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80
ext $20 msg $200;
%if &fref=0 %then %do; %if &fref=0 %then %do;
rc = filename(fref, "&path"); rc = filename(fref, "&path");
%end; %end;

View File

@@ -3,25 +3,29 @@
@brief Create a CARDS file from a SAS dataset. @brief Create a CARDS file from a SAS dataset.
@details Uses dataset attributes to convert all data into datalines. @details Uses dataset attributes to convert all data into datalines.
Running the generated file will rebuild the original dataset. Running the generated file will rebuild the original dataset.
usage: Usage:
%mp_ds2cards(base_ds=sashelp.class %mp_ds2cards(base_ds=sashelp.class
, cards_file= "C:\temp\class.sas" , cards_file= "C:\temp\class.sas"
, maxobs=5) , maxobs=5)
stuff to add TODO:
- labelling the dataset - labelling the dataset
- explicity setting a unix LF - explicity setting a unix LF
- constraints / indexes etc - constraints / indexes etc
@param base_ds= Should be two level - eg work.blah. This is the table that @param [in] base_ds= Should be two level - eg work.blah. This is the table
is converted to a cards file. that is converted to a cards file.
@param tgt_ds= Table that the generated cards file would create. Optional - @param [in] tgt_ds= Table that the generated cards file would create.
if omitted, will be same as BASE_DS. Optional - if omitted, will be same as BASE_DS.
@param cards_file= Location in which to write the (.sas) cards file @param [out] cards_file= Location in which to write the (.sas) cards file
@param maxobs= to limit output to the first <code>maxobs</code> observations @param [in] maxobs= to limit output to the first <code>maxobs</code>
@param showlog= whether to show generated cards file in the SAS log (YES/NO) observations
@param outencoding= provide encoding value for file statement (eg utf-8) @param [in] showlog= whether to show generated cards file in the SAS log
(YES/NO)
@param [in] outencoding= provide encoding value for file statement (eg utf-8)
@param [in] append= If NO then will rebuild the cards file if it already
exists, otherwise will append to it. Used by the mp_lib2cards.sas macro.
@version 9.2 @version 9.2
@@ -34,6 +38,7 @@
,random_sample=NO ,random_sample=NO
,showlog=YES ,showlog=YES
,outencoding= ,outencoding=
,append=NO
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local i setds nvars; %local i setds nvars;
@@ -46,6 +51,8 @@
%if (&tgt_ds = ) %then %let tgt_ds=&base_ds; %if (&tgt_ds = ) %then %let tgt_ds=&base_ds;
%if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.); %if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.);
%if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding"; %if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding";
%if ("&append" = "") %then %let append=;
%else %let append=mod;
/* get varcount */ /* get varcount */
%let nvars=0; %let nvars=0;
@@ -59,9 +66,11 @@ select count(*) into: nvars from dictionary.columns
%end; %end;
/* get indexes */ /* get indexes */
proc sort data=sashelp.vindex proc sort
(where=(upcase(libname)="%scan(%upcase(&base_ds),1)" data=sashelp.vindex(
and upcase(memname)="%scan(%upcase(&base_ds),2)")) where=(upcase(libname)="%scan(%upcase(&base_ds),1)"
and upcase(memname)="%scan(%upcase(&base_ds),2)")
)
out=_data_; out=_data_;
by indxname indxpos; by indxname indxpos;
run; run;
@@ -172,7 +181,7 @@ data _null_;
run; run;
data _null_; data _null_;
file &cards_file. &outencoding lrecl=32767 termstr=nl; file &cards_file. &outencoding lrecl=32767 termstr=nl &append;
length __attrib $32767; length __attrib $32767;
if _n_=1 then do; if _n_=1 then do;
put '/*******************************************************************'; put '/*******************************************************************';

View File

@@ -133,7 +133,9 @@ run;
if notnull='yes' then notnul=' not null'; if notnull='yes' then notnul=' not null';
if notnull='no' and missing(label) then put ' ' name typ; if notnull='no' and missing(label) then put ' ' name typ;
else if notnull='yes' and missing(label) then put ' ' name typ '[' notnul ']'; else if notnull='yes' and missing(label) then do;
put ' ' name typ '[' notnul ']';
end;
else if notnull='no' then put ' ' name typ '[' lab ']'; else if notnull='no' then put ' ' name typ '[' lab ']';
else put ' ' name typ '[' notnul ',' lab ']'; else put ' ' name typ '[' notnul ',' lab ']';
@@ -166,7 +168,7 @@ run;
call symputx('constcheck',1); call symputx('constcheck',1);
end; end;
if last then call symputx('constraints_used',cats(upcase(constraints_used))); if last then call symput('constraints_used',cats(upcase(constraints_used)));
length curds const col $39; length curds const col $39;
curds="&curds"; curds="&curds";
@@ -176,7 +178,8 @@ run;
proc append base=&pkds data=&syslast;run; proc append base=&pkds data=&syslast;run;
/* Create Unique Indexes, but only if they were not already defined within the Constraints section. */ /* Create Unique Indexes, but only if they were not already defined within
the Constraints section. */
data _data_(keep=curds const col); data _data_(keep=curds const col);
set &idxinfo (where=( set &idxinfo (where=(
libname="%scan(&curds,1,.)" libname="%scan(&curds,1,.)"
@@ -187,7 +190,7 @@ run;
file &outref mod; file &outref mod;
by idxusage indxname; by idxusage indxname;
name=upcase(name); name=upcase(name);
if &constcheck=1 then stop; /* in fact we only care about PKs so stop if we have */ if &constcheck=1 then stop; /* we only care about PKs so stop if we have */
if _n_=1 and &constcheck=0 then put / ' indexes {'; if _n_=1 and &constcheck=0 then put / ' indexes {';
length cols $5000; length cols $5000;
@@ -261,7 +264,11 @@ run;
line='Ref: "'!!"&curds" line='Ref: "'!!"&curds"
!!cats('".(',"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))",')') !!cats('".(',"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))",')')
!!' - ' !!' - '
!!cats(quote(trim(curds)),'.(',"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))",')'); !!cats(quote(trim(curds))
,'.('
,"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))"
,')'
);
put line; put line;
run; run;
@@ -282,7 +289,9 @@ run;
create table &pkds.5b as create table &pkds.5b as
select curds,count(*) as cnt select curds,count(*) as cnt
from &pkds.5a from &pkds.5a
where curds not in (select curds from &pkds.2 where cols="&pkcols") /* not a one to one match */ where curds not in (
select curds from &pkds.2 where cols="&pkcols"
) /* not a one to one match */
and curds ne "&curds" /* exclude self */ and curds ne "&curds" /* exclude self */
group by 1; group by 1;
create table &pkds.6 as create table &pkds.6 as

View File

@@ -86,7 +86,9 @@ create table _data_ as
%global constraints_used; %global constraints_used;
data _null_; data _null_;
length ctype $11 constraint_name_orig $256 constraints_used $5000; length ctype $11 constraint_name_orig $256 constraints_used $5000;
set &colconst (where=(table_name="&curds" and constraint_type in ('PRIMARY','UNIQUE'))) end=last; set &colconst(
where=(table_name="&curds" and constraint_type in ('PRIMARY','UNIQUE'))
) end=last;
file &fref mod; file &fref mod;
by constraint_type constraint_name; by constraint_type constraint_name;
retain constraints_used; retain constraints_used;
@@ -161,10 +163,19 @@ run;
put ');'; put ');';
run; run;
/* Create Unique Indexes, but only if they were not already defined within the Constraints section. */ /* Create Unique Indexes, but only if they were not already defined within
the Constraints section. */
data _null_; data _null_;
*length ds $128; *length ds $128;
set &idxinfo (where=(memname="&curds" and unique='yes' and indxname not in (%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))))); set &idxinfo(
where=(
memname="&curds"
and unique='yes'
and indxname not in (
%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))
)
)
);
file &fref mod; file &fref mod;
by idxusage indxname; by idxusage indxname;
/* ds=cats(libname,'.',memname); */ /* ds=cats(libname,'.',memname); */
@@ -228,10 +239,19 @@ run;
/* Extra step for data constraints */ /* Extra step for data constraints */
%addConst() %addConst()
/* Create Unique Indexes, but only if they were not already defined within the Constraints section. */ /* Create Unique Indexes, but only if they were not already defined within
the Constraints section. */
data _null_; data _null_;
*length ds $128; *length ds $128;
set &idxinfo (where=(memname="&curds" and unique='yes' and indxname not in (%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))))); set &idxinfo(
where=(
memname="&curds"
and unique='yes'
and indxname not in (
%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))
)
)
);
file &fref mod; file &fref mod;
by idxusage indxname; by idxusage indxname;
*ds=cats(libname,'.',memname); *ds=cats(libname,'.',memname);
@@ -320,10 +340,19 @@ run;
put ');'; put ');';
run; run;
/* Create Unique Indexes, but only if they were not already defined within the Constraints section. */ /* Create Unique Indexes, but only if they were not already defined within
the Constraints section. */
data _null_; data _null_;
*length ds $128; *length ds $128;
set &idxinfo (where=(memname="&curds" and unique='yes' and indxname not in (%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))))); set &idxinfo(
where=(
memname="&curds"
and unique='yes'
and indxname not in (
%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))
)
)
);
file &fref mod; file &fref mod;
by idxusage indxname; by idxusage indxname;
/* ds=cats(libname,'.',memname); */ /* ds=cats(libname,'.',memname); */

View File

@@ -8,10 +8,11 @@
- NAME Name of the base dataset column - NAME Name of the base dataset column
- MAXLEN Maximum length of the data contained therein. - MAXLEN Maximum length of the data contained therein.
Character fields may be allocated very large widths (eg 32000) of which the maximum Character fields may be allocated very large widths (eg 32000) of which the
value is likely to be much narrower. This macro was designed to enable a HTML maximum value is likely to be much narrower. This macro was designed to
table to be appropriately sized however this could be used as part of a data enable a HTML table to be appropriately sized however this could be used as
audit to ensure we aren't over-sizing our tables in relation to the data therein. part of a data audit to ensure we aren't over-sizing our tables in relation to
the data therein.
Numeric fields are converted using the relevant format to determine the width. Numeric fields are converted using the relevant format to determine the width.
Usage: Usage:

View File

@@ -83,7 +83,8 @@
%end; %end;
data _null_;file &jref mod ; data _null_;file &jref mod ;
put "["; call symputx('cols',0,'l'); put "["; call symputx('cols',0,'l');
proc sort data=sashelp.vcolumn(where=(libname='WORK' & memname="%upcase(&ds)")) proc sort
data=sashelp.vcolumn(where=(libname='WORK' & memname="%upcase(&ds)"))
out=_data_; out=_data_;
by varnum; by varnum;
@@ -122,7 +123,8 @@
%end; %end;
%end; %end;
run; run;
/* write to temp loc to avoid _webout truncation - https://support.sas.com/kb/49/325.html */ /* write to temp loc to avoid _webout truncation
- https://support.sas.com/kb/49/325.html */
filename _sjs temp lrecl=131068 encoding='utf-8'; filename _sjs temp lrecl=131068 encoding='utf-8';
data _null_; file _sjs lrecl=131068 encoding='utf-8' mod; data _null_; file _sjs lrecl=131068 encoding='utf-8' mod;
set &tempds; set &tempds;

View File

@@ -1,21 +1,35 @@
/** /**
@file @file
@brief Convert all library members to CARDS files @brief Convert all library members to CARDS files
@details Gets list of members then calls the <code>%mp_ds2cards()</code> @details Gets list of members then calls the <code>%mp_ds2cards()</code> macro.
macro Usage:
usage:
%mp_lib2cards(lib=sashelp %mp_lib2cards(lib=sashelp
, outloc= C:\temp ) , outloc= C:\temp )
The output will be one cards file in the `outloc` directory per dataset in the
input `lib` library. If the `outloc` directory does not exist, it is created.
To create a single SAS file with the first 1000 records of each table in a
library you could use this syntax:
%mp_lib2cards(lib=sashelp
, outloc= /tmp
, outfile= myfile.sas
, maxobs= 1000
)
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_mkdir.sas @li mf_mkdir.sas
@li mf_trimstr.sas
@li mp_ds2cards.sas @li mp_ds2cards.sas
@param lib= Library in which to convert all datasets @param [in] lib= Library in which to convert all datasets
@param outloc= Location in which to store output. Defaults to WORK library. @param [out] outloc= Location in which to store output. Defaults to WORK
Do not use a trailing slash (my/path not my/path/). No quotes. library. No quotes.
@param maxobs= limit output to the first <code>maxobs</code> observations @param [out] outfile= Optional output file NAME - if provided, then will create
a single output file instead of one file per input table.
@param [in] maxobs= limit output to the first <code>maxobs</code> observations
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
@@ -25,6 +39,7 @@
,outloc=%sysfunc(pathname(work)) /* without trailing slash */ ,outloc=%sysfunc(pathname(work)) /* without trailing slash */
,maxobs=max ,maxobs=max
,random_sample=NO ,random_sample=NO
,outfile=0
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
/* Find the tables */ /* Find the tables */
@@ -36,6 +51,10 @@ select distinct lowcase(memname)
from dictionary.tables from dictionary.tables
where upcase(libname)="%upcase(&lib)"; where upcase(libname)="%upcase(&lib)";
/* trim trailing slash, if provided */
%let outloc=%mf_trimstr(&outloc,/);
%let outloc=%mf_trimstr(&outloc,\);
/* create the output directory */ /* create the output directory */
%mf_mkdir(&outloc) %mf_mkdir(&outloc)
@@ -43,9 +62,17 @@ select distinct lowcase(memname)
%do x=1 %to %sysfunc(countw(&memlist)); %do x=1 %to %sysfunc(countw(&memlist));
%let ds=%scan(&memlist,&x); %let ds=%scan(&memlist,&x);
%mp_ds2cards(base_ds=&lib..&ds %mp_ds2cards(base_ds=&lib..&ds
,cards_file="&outloc/&ds..sas"
,maxobs=&maxobs ,maxobs=&maxobs
,random_sample=&random_sample) ,random_sample=&random_sample
%if "&outfile" ne "0" %then %do;
,append=YES
,cards_file="&outloc/&outfile"
%end;
%else %do;
,append=NO
,cards_file="&outloc/&ds..sas"
%end;
)
%end; %end;
%mend; %mend;

98
base/mp_mdtablewrite.sas Normal file
View File

@@ -0,0 +1,98 @@
/**
@file
@brief Create a Markdown Table from a dataset
@details A markdown table is a simple table representation for use in
documents written in markdown format.
An online generator is available here:
https://www.tablesgenerator.com/markdown_tables
This structure is also used by the Macro Core library for documenting input/
output datasets, as well as the sasjs/cli tool for documenting inputs/outputs
for web services.
We take the standard definition one step further by embedding the informat
in the table header row, like so:
|var1:$|var2:best.|var3:date9.|
|---|---|---|
|some text|42|01JAN1960|
|blah|1|31DEC1999|
Which resolves to:
|var1:$|var2:best.|var3:date9.|
|---|---|---|
|some text|42|01JAN1960|
|blah|1|31DEC1999|
Usage:
%mp_mdtablewrite(libds=sashelp.class,showlog=YES)
<h4> SAS Macros </h4>
@li mf_getvarlist.sas
@li mf_getvarformat.sas
@param [in] libds= the library / dataset to create or read from.
@param [out] fref= Fileref to contain the markdown. Default=mdtable.
@param [out] showlog= set to YES to show the markdown in the log. Default=NO.
@version 9.3
@author Allan Bowe
**/
%macro mp_mdtablewrite(
libds=,
fref=mdtable,
showlog=NO
)/*/STORE SOURCE*/;
/* check fileref is assigned */
%if %sysfunc(fileref(&fref)) > 0 %then %do;
filename &fref temp;
%end;
%local vars;
%let vars=%mf_getvarlist(&libds);
/* create the header row */
data _null_;
file &fref;
length line $32767;
put '|'
%local i var fmt;
%do i=1 %to %sysfunc(countw(&vars));
%let var=%scan(&vars,&i);
%let fmt=%mf_getvarformat(&libds,&var,force=1);
"&var:&fmt|"
%end;
;
put '|'
%do i=1 %to %sysfunc(countw(&vars));
"---|"
%end;
;
run;
/* write out the data */
data _null_;
file &fref mod dlm='|' lrecl=32767;
set &libds ;
length line $32767;
line=cats('|',%mf_getvarlist(&libds,dlm=%str(,'|',)),'|');
put line;
run;
%if %upcase(&showlog)=YES %then %do;
options ps=max;
data _null_;
infile &fref;
input;
putlog _infile_;
run;
%end;
%mend mp_mdtablewrite;

View File

@@ -1,7 +1,8 @@
/** /**
@file @file
@brief Reset an option to original value @brief Reset an option to original value
@details Inspired by the SAS Jedi - https://blogs.sas.com/content/sastraining/2012/08/14/jedi-sas-tricks-reset-sas-system-options/ @details Inspired by the SAS Jedi -
https://blogs.sas.com/content/sastraining/2012/08/14/jedi-sas-tricks-reset-sas-system-options
Called as follows: Called as follows:
options obs=30; options obs=30;

View File

@@ -20,9 +20,10 @@
@param ds= the dataset to search (leave blank to search entire library) @param ds= the dataset to search (leave blank to search entire library)
@param string= the string value to search @param string= the string value to search
@param numval= the numeric value to search (must be exact) @param numval= the numeric value to search (must be exact)
@param outloc= the directory in which to create the output datasets with matching @param outloc= the directory in which to create the output datasets with
rows. Will default to a subfolder in the WORK library. matching rows. Will default to a subfolder in the WORK library.
@param outobs= set to a positive integer to restrict the number of observations @param outobs= set to a positive integer to restrict the number of
observations
@param filter_text= add a (valid) filter clause to further filter the results @param filter_text= add a (valid) filter clause to further filter the results
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@@ -44,7 +45,8 @@
,filter_text=%str(1=1) ,filter_text=%str(1=1)
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local table_list table table_num table colnum col start_tm check_tm vars type coltype; %local table_list table table_num table colnum col start_tm check_tm vars type
coltype;
%put process began at %sysfunc(datetime(),datetime19.); %put process began at %sysfunc(datetime(),datetime19.);
%if &syscc ge 4 %then %do; %if &syscc ge 4 %then %do;
@@ -101,7 +103,8 @@ proc sql
%end; %end;
%end; %end;
); );
%put Search query for &table took %sysevalf(%sysfunc(datetime())-&check_tm) seconds; %put Search query for &table took
%sysevalf(%sysfunc(datetime())-&check_tm) seconds;
%if &sqlrc ne 0 %then %do; %if &sqlrc ne 0 %then %do;
%put %str(WAR)NING: SQLRC=&sqlrc when processing &table; %put %str(WAR)NING: SQLRC=&sqlrc when processing &table;
%return; %return;

View File

@@ -26,7 +26,7 @@
%if not (%mf_existds(&libds)) %then %do; %if not (%mf_existds(&libds)) %then %do;
data &libds (index=(key/unique)); data &libds (index=(key/unique));
length key $32 valc $256 valn 8 type $1; length key $64 valc $2048 valn 8 type $1;
call missing(of _all_); call missing(of _all_);
stop; stop;
run; run;

View File

@@ -1,7 +1,8 @@
/** /**
@file @file
@brief Capture session start / finish times and request details @brief Capture session start / finish times and request details
@details For details, see http://www.rawsas.com/2015/09/logging-of-stored-process-server.html. @details For details, see
https://rawsas.com/event-logging-of-stored-process-server-sessions.
Requires a base table in the following structure (name can be changed): Requires a base table in the following structure (name can be changed):
proc sql; proc sql;

View File

@@ -6,7 +6,8 @@
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;
%mp_streamfile(contenttype=csv,inloc=/some/where.txt,outname=myfile.txt) %mp_streamfile(contenttype=csv,inloc=/some/where.txt,outname=myfile.txt)
@@ -65,13 +66,15 @@
%else %if &contentype=XLSX %then %do; %else %if &contentype=XLSX %then %do;
%if &platform=SASMETA %then %do; %if &platform=SASMETA %then %do;
data _null_; data _null_;
rc=stpsrv_header('Content-type','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); rc=stpsrv_header('Content-type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
run; run;
%end; %end;
%else %if &platform=SASVIYA %then %do; %else %if &platform=SASVIYA %then %do;
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.xls' filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.xls'
contenttype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' contenttype=
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
contentdisp="attachment; filename=&outname"; contentdisp="attachment; filename=&outname";
%end; %end;
%end; %end;

92
base/mp_testjob.sas Normal file
View File

@@ -0,0 +1,92 @@
/**
@file
@brief Runs arbitrary code for a specified amount of time
@details Executes a series of procs and data steps to enable performance
testing of arbitrary jobs.
%mp_testjob(
duration=60*5
)
@param [in] duration= the time in seconds which the job should run for. Actual
time may vary, as the check is done in between steps. Default = 30 (seconds).
<h4> SAS Macros </h4>
@li mf_getuniquelibref.sas
@li mf_getuniquename.sas
@li mf_mkdir.sas
@version 9.4
@author Allan Bowe
**/
%macro mp_testjob(duration=30
)/*/STORE SOURCE*/;
%local lib dir ds1 ds2 ds3 start_tm i;
%let start_tm=%sysfunc(datetime());
%let duration=%sysevalf(&duration);
/* create a temporary library in WORK */
%let lib=%mf_getuniquelibref();
%let dir=%mf_getuniquename();
%mf_mkdir(%sysfunc(pathname(work))/&dir)
libname &lib "%sysfunc(pathname(work))/&dir";
/* loop through until time expires */
%let ds1=%mf_getuniquename();
%let ds2=%mf_getuniquename();
%let ds3=%mf_getuniquename();
%do i=0 %to 1;
/* create big dataset */
data &lib..&ds1(compress=no );
do x=1 to 1000000;
randnum0=ranuni(0)*3;
randnum1=ranuni(0)*2;
bigchar=repeat('A',300);
output;
end;
run;
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
proc summary ;
class randnum0 randnum1;
output out=&lib..&ds2;
run;quit;
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
/* add more data */
proc sql;
create table &lib..&ds3 as
select *, ranuni(0)*10 as randnum2
from &lib..&ds1
order by randnum1;
quit;
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
proc sort data=&lib..&ds3;
by descending x;
run;
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
/* wait 5 seconds */
data _null_;
call sleep(5,1);
run;
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
%let i=0;
%end;
%gate:
%put time is up!;
proc datasets lib=&lib kill;
run;
quit;
libname &lib clear;
%mend;

View File

@@ -8,8 +8,11 @@
Credits: Credits:
* Roger Deangelis, https://communities.sas.com/t5/SAS-Programming/listing-all-files-within-a-directory-and-subdirectories/m-p/332616/highlight/true#M74887 Roger Deangelis:
* Tom, https://communities.sas.com/t5/SAS-Programming/listing-all-files-of-all-types-from-all-subdirectories/m-p/334113/highlight/true#M75419 https://communities.sas.com/t5/SAS-Programming/listing-all-files-within-a-directory-and-subdirectories/m-p/332616/highlight/true#M74887
Tom:
https://communities.sas.com/t5/SAS-Programming/listing-all-files-of-all-types-from-all-subdirectories/m-p/334113/highlight/true#M75419
@param dir= Directory to be scanned (default=/tmp) @param dir= Directory to be scanned (default=/tmp)

View File

@@ -1,6 +0,0 @@
ECHO on
rmdir /s /q C:\DEVELOPMENT\output
mkdir C:\DEVELOPMENT\output
doxygen.exe Doxyfile

View File

@@ -1,45 +0,0 @@
#!/bin/bash
####################################################################
# PROJECT: Macro Core Docs Build #
####################################################################
BUILD_FOLDER="/tmp/macrocore_docs"
# move to project root
cd ..
# create build directory
rm -rf $BUILD_FOLDER
mkdir $BUILD_FOLDER
# copy relevant files
cp -r base $BUILD_FOLDER
cp -r meta $BUILD_FOLDER
cp -r metax $BUILD_FOLDER
cp -r lua $BUILD_FOLDER
cp -r viya $BUILD_FOLDER
cp -r doxy $BUILD_FOLDER
cp main.dox $BUILD_FOLDER
cp doxy/Doxyfile $BUILD_FOLDER
# update Doxyfile and generate
cd $BUILD_FOLDER
echo "OUTPUT_DIRECTORY=$BUILD_FOLDER/out" >> $BUILD_FOLDER/Doxyfile
echo "INPUT+=main.dox" >> $BUILD_FOLDER/Doxyfile
doxygen Doxyfile
# refresh github pages site
git clone git@github.com:sasjs/core.github.io.git
cd core.github.io
rm -r *
mv $BUILD_FOLDER/out/doxy/* .
echo 'core.sasjs.io' > CNAME
git add .
git commit -m "build.sh build on $(date +%F:%H:%M:%S)"
git push
npx sitemap-generator-cli https://core.sasjs.io
git add .
git commit -m "adding sitemap"
git push
echo "check it out: https://sasjs.github.io/core.github.io/files.html"

View File

@@ -1,23 +0,0 @@
<!-- HTML footer for doxygen 1.8.17-->
<!-- start footer part -->
<!--BEGIN GENERATE_TREEVIEW-->
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
<ul>
$navpath
<li class="footer">$generatedby
<a href="https://www.doxygen.org/index.html">
<img class="footer" src="$relpath^doxygen.png" alt="doxygen"/></a> $doxygenversion </li>
<i> For more information visit the </i> <a href="https://github.com/sasjs/core">Macro Core library</a>.</li>
</ul>
</div>
<!--END GENERATE_TREEVIEW-->
<!--BEGIN !GENERATE_TREEVIEW-->
<hr class="footer"/><address class="footer"><small>
$generatedby &#160;<a href="http://www.doxygen.org/index.html">
<img class="footer" src="$relpath^doxygen.png" alt="doxygen"/>
</a> $doxygenversion
</small></address>
<!--END !GENERATE_TREEVIEW-->
</body>
</html>

View File

@@ -1,72 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- HTML header for doxygen 1.8.17-->
<html xmlns="https://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
<meta name="generator" content="Doxygen $doxygenversion"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview
$search
$mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
<link REL="icon" HREF="https://sasjs.io/img/runningman.jpg">
$extrastylesheet
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px;">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo">
<img alt="Logo" src="$relpath^$projectlogo"/></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td id="projectalign" style="padding-left: 0.5em;">
<div id="projectname">
<!--BEGIN PROJECT_NUMBER-->&#160;<span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">
Production Ready Macros for SAS Application Developers</br>
<a href="https://github.com/sasjs/core">
https://github.com/sasjs/core
</a>
</div>
<meta name="Description" content="$projectbrief">
<!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td style="padding-left: 0.5em;">
<div id="projectbrief">$projectbrief</div>
<table style="padding-left: 2em;" cellspacing="0" cellpadding="0">
<tr><td> Production Ready Macros for SAS Application Developers</td></tr>
<tr><td><a href="https://github.com/sasjs/core">
https://github.com/sasjs/core
</a></td></tr>
</table>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<td>$searchbox</td>
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->

View File

@@ -1,5 +0,0 @@
#projectlogo img
{
border: 0px none;
max-height:70px
}

View File

@@ -117,7 +117,8 @@ run;
%end; %end;
%else %if &engine=REMOTE %then %do; %else %if &engine=REMOTE %then %do;
data x; data x;
length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName Delimiter $256 properties $2048; length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName
Delimiter $256 properties $2048;
retain properties; retain properties;
rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon); rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);
@@ -129,7 +130,8 @@ run;
rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue); rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);
rc = metadata_getattr(uriProp , "PropertyName",PropertyName); rc = metadata_getattr(uriProp , "PropertyName",PropertyName);
rc = metadata_getattr(uriProp , "Delimiter",Delimiter); rc = metadata_getattr(uriProp , "Delimiter",Delimiter);
properties = trim(properties) !! " " !! trim(PropertyName) !! trim(Delimiter) !! trim(PropertyValue); properties = trim(properties) !! " " !! trim(PropertyName)
!! trim(Delimiter) !! trim(PropertyValue);
output; output;
k+1; k+1;
rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp); rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);
@@ -170,7 +172,8 @@ run;
else if value='Connection.OLE.Property.PROVIDER.Name.xmlKey.txt' then do; else if value='Connection.OLE.Property.PROVIDER.Name.xmlKey.txt' then do;
rc4=metadata_getattr(conprop_uri,'DefaultValue',provider); rc4=metadata_getattr(conprop_uri,'DefaultValue',provider);
end; end;
else if value='Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt' then do; else if value='Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt' then
do;
rc5=metadata_getattr(conprop_uri,'DefaultValue',properties); rc5=metadata_getattr(conprop_uri,'DefaultValue',properties);
end; end;
end; end;
@@ -357,7 +360,8 @@ run;
call symputx('authdomain',authdomain,'l'); call symputx('authdomain',authdomain,'l');
/* path */ /* path */
rc=metadata_getprop(assocuri1,'Connection.Oracle.Property.PATH.Name.xmlKey.txt',path); rc=metadata_getprop(assocuri1,
'Connection.Oracle.Property.PATH.Name.xmlKey.txt',path);
call symputx('path',path,'l'); call symputx('path',path,'l');
/* schema */ /* schema */
@@ -366,14 +370,16 @@ run;
call symputx('schema',schema,'l'); call symputx('schema',schema,'l');
run; run;
%put NOTE: Executing the following:/; %put NOTE-; %put NOTE: Executing the following:/; %put NOTE-;
%put NOTE- libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain; %put NOTE- libname &libref ORACLE path=&path schema=&schema;
%put NOTE- authdomain=&authdomain;
%put NOTE-; %put NOTE-;
libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain; libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;
%end; %end;
%else %if &engine=SQLSVR %then %do; %else %if &engine=SQLSVR %then %do;
%put NOTE: Obtaining &engine library details; %put NOTE: Obtaining &engine library details;
data _null; data _null;
length assocuri1 assocuri2 assocuri3 authdomain path schema userid passwd $256; length assocuri1 assocuri2 assocuri3 authdomain path schema userid
passwd $256;
call missing (of _all_); call missing (of _all_);
rc=metadata_getnasn("&liburi",'DefaultLogin',1,assocuri1); rc=metadata_getnasn("&liburi",'DefaultLogin',1,assocuri1);
@@ -384,7 +390,8 @@ run;
/* path */ /* path */
rc=metadata_getnasn("&liburi",'LibraryConnection',1,assocuri2); rc=metadata_getnasn("&liburi",'LibraryConnection',1,assocuri2);
rc=metadata_getprop(assocuri2,'Connection.SQL.Property.Datasrc.Name.xmlKey.txt',path); rc=metadata_getprop(assocuri2,
'Connection.SQL.Property.Datasrc.Name.xmlKey.txt',path);
call symputx('path',path,'l'); call symputx('path',path,'l');
/* schema */ /* schema */
@@ -394,7 +401,8 @@ run;
run; run;
%put NOTE: Executing the following:/; %put NOTE-; %put NOTE: Executing the following:/; %put NOTE-;
%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="XXX"; %put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;
%put NOTE- user="&user" pass="XXX";
%put NOTE-; %put NOTE-;
libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass"; libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";
@@ -402,7 +410,8 @@ run;
%else %if &engine=TERADATA %then %do; %else %if &engine=TERADATA %then %do;
%put NOTE: Obtaining &engine library details; %put NOTE: Obtaining &engine library details;
data _null; data _null;
length assocuri1 assocuri2 assocuri3 authdomain path schema userid passwd $256; length assocuri1 assocuri2 assocuri3 authdomain path schema userid
passwd $256;
call missing (of _all_); call missing (of _all_);
/* get auth domain */ /* get auth domain */
@@ -421,7 +430,8 @@ run;
/* path */ /* path */
rc=metadata_getnasn("&liburi",'LibraryConnection',1,assocuri2); rc=metadata_getnasn("&liburi",'LibraryConnection',1,assocuri2);
rc=metadata_getprop(assocuri2,'Connection.Teradata.Property.SERVER.Name.xmlKey.txt',path); rc=metadata_getprop(assocuri2,
'Connection.Teradata.Property.SERVER.Name.xmlKey.txt',path);
call symputx('path',path,'l'); call symputx('path',path,'l');
/* schema */ /* schema */
@@ -431,7 +441,8 @@ run;
run; run;
%put NOTE: Executing the following:/; %put NOTE-; %put NOTE: Executing the following:/; %put NOTE-;
%put NOTE- libname &libref TERADATA server=&path schema=&schema authdomain=&authdomain; %put NOTE- libname &libref TERADATA server=&path schema=&schema ;
%put NOTe- authdomain=&authdomain;
%put NOTE-; %put NOTE-;
libname &libref TERADATA server=&path schema=&schema authdomain=&authdomain; libname &libref TERADATA server=&path schema=&schema authdomain=&authdomain;

View File

@@ -14,7 +14,8 @@
@li mp_abort.sas @li mp_abort.sas
@param libref the libref (not name) of the metadata library @param libref the libref (not name) of the metadata library
@param mAbort= If not assigned, HARD will call %mp_abort(), SOFT will silently return @param mAbort= If not assigned, HARD will call %mp_abort(), SOFT will
silently return
@returns libname statement @returns libname statement

View File

@@ -13,7 +13,9 @@
,params= name1=value1&#x0a;name2=value2&#x0a;emptyvalue= ,params= name1=value1&#x0a;name2=value2&#x0a;emptyvalue=
) )
@warning application components do not get deleted when removing the container folder! be sure you have the administrative priviliges to remove this kind of metadata from the SMC plugin (or be ready to do to so programmatically). @warning application components do not get deleted when removing the container
folder! be sure you have the administrative priviliges to remove this kind of
metadata from the SMC plugin (or be ready to do to so programmatically).
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_abort.sas @li mp_abort.sas

View File

@@ -55,7 +55,7 @@ data _null_;
* must have a starting slash ; * must have a starting slash ;
if ( substr(folderPath,1,1) ne '/' ) then do; if ( substr(folderPath,1,1) ne '/' ) then do;
put "%str(ERR)OR: &sysmacroname PATH parameter value must have starting slash"; put "%str(ERR)OR: &sysmacroname PATH param value must have starting slash";
stop; stop;
end; end;
@@ -123,9 +123,10 @@ run;
%local inmeta; %local inmeta;
%put creating: &path; %put creating: &path;
%let inmeta=<AddMetadata><Reposid>$METAREPOSITORY</Reposid><Metadata> %let inmeta=<AddMetadata><Reposid>$METAREPOSITORY</Reposid><Metadata>
<Tree Name='&child' PublicType='Folder' TreeType='BIP Folder' UsageVersion='1000000'> <Tree Name='&child' PublicType='Folder' TreeType='BIP Folder'
<ParentTree><Tree ObjRef='&parentFolderObjId'/></ParentTree></Tree></Metadata> UsageVersion='1000000'><ParentTree><Tree ObjRef='&parentFolderObjId'/>
<NS>SAS</NS><Flags>268435456</Flags></AddMetadata>; </ParentTree></Tree></Metadata><NS>SAS</NS><Flags>268435456</Flags>
</AddMetadata>;
proc metadata in="&inmeta" out=__newdir verbose; proc metadata in="&inmeta" out=__newdir verbose;
run ; run ;

View File

@@ -166,7 +166,7 @@ filename &frefout temp;
putlog (_all_)(=); putlog (_all_)(=);
run; run;
%if &checktype ne Prototype %then %do; %if &checktype ne Prototype %then %do;
%put %str(ERR)OR: Prototype (Library.SAS.Prototype.Name.xmlKey.txt) not found!; %put %str(ERR)OR: Prototype Library.SAS.Prototype.Name.xmlKey.txt not found;
%return; %return;
%end; %end;

View File

@@ -210,7 +210,8 @@ run;
rc3=METADATA_SETATTR(prompturi, 'GroupType','2'); rc3=METADATA_SETATTR(prompturi, 'GroupType','2');
rc4=METADATA_SETATTR(prompturi, 'Name','Parameters'); rc4=METADATA_SETATTR(prompturi, 'Name','Parameters');
rc5=METADATA_SETATTR(prompturi, 'PublicType','Embedded:PromptGroup'); rc5=METADATA_SETATTR(prompturi, 'PublicType','Embedded:PromptGroup');
GroupInfo="<PromptGroup promptId='PromptGroup_%sysfunc(datetime())_&sysprocessid'" GroupInfo=
"<PromptGroup promptId='PromptGroup_%sysfunc(datetime())_&sysprocessid'"
!!" version='1.0'><Label><Text xml:lang='en-GB'>Parameters</Text>" !!" version='1.0'><Label><Text xml:lang='en-GB'>Parameters</Text>"
!!"</Label></PromptGroup>"; !!"</Label></PromptGroup>";
rc6 = METADATA_SETATTR(prompturi, 'GroupInfo',groupinfo); rc6 = METADATA_SETATTR(prompturi, 'GroupInfo',groupinfo);

View File

@@ -22,7 +22,7 @@ Usage:
%webout(OBJ,example2) * Object format, easier to work with ; %webout(OBJ,example2) * Object format, easier to work with ;
%webout(CLOSE) %webout(CLOSE)
;;;; ;;;;
%mm_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001,replace=YES) %mm_createwebservice(path=/Public/app/common,name=appInit)
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mm_createstp.sas @li mm_createstp.sas
@@ -119,7 +119,8 @@ data _null_;
put ' %end; '; put ' %end; ';
put ' data _null_;file &jref mod ; '; put ' data _null_;file &jref mod ; ';
put ' put "["; call symputx(''cols'',0,''l''); '; put ' put "["; call symputx(''cols'',0,''l''); ';
put ' proc sort data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) '; put ' proc sort ';
put ' data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) ';
put ' out=_data_; '; put ' out=_data_; ';
put ' by varnum; '; put ' by varnum; ';
put ' '; put ' ';
@@ -158,7 +159,8 @@ data _null_;
put ' %end; '; put ' %end; ';
put ' %end; '; put ' %end; ';
put ' run; '; put ' run; ';
put ' /* write to temp loc to avoid _webout truncation - https://support.sas.com/kb/49/325.html */ '; put ' /* write to temp loc to avoid _webout truncation ';
put ' - https://support.sas.com/kb/49/325.html */ ';
put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; '; put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; '; put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; ';
put ' set &tempds; '; put ' set &tempds; ';

92
meta/mm_deletelibrary.sas Normal file
View File

@@ -0,0 +1,92 @@
/**
@file
@brief Deletes a library by Name
@details Used to delete a library.
Usage:
%* create a library in the home directory ;
%mm_createlibrary(
libname=My Temp Library,
libref=XXTEMPXX,
tree=/User Folders/&sysuserid,
directory=%sysfunc(pathname(work))
)
%* delete the library ;
%mm_deletelibrary(name=My Temp Library)
After running the above, the following will be shown in the log:
![](https://i.imgur.com/Y4Tog24.png)
@param [in] name= the name (not libref) of the library to be deleted
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mp_abort.sas
@version 9.4
@author Allan Bowe
**/
%macro mm_deletelibrary(
name=
)/*/STORE SOURCE*/;
/**
* Check if library exists and get uri
*/
data _null_;
length type uri $256;
rc=metadata_resolve("omsobj:SASLibrary?@Name='&name'",type,uri);
call symputx('checktype',type,'l');
call symputx('liburi',uri,'l');
putlog (_all_)(=);
run;
%if &checktype ne SASLibrary %then %do;
%put &sysmacroname: Library (&name) was not found, and so will not be deleted;
%return;
%end;
%local fname1 fname2;
%let fname1=%mf_getuniquefileref();
%let fname2=%mf_getuniquefileref();
filename &fname1 temp lrecl=10000;
filename &fname2 temp lrecl=10000;
data _null_ ;
file &fname1 ;
put "<DeleteMetadata><Metadata><SASLibrary Id='&liburi'/>";
put "</Metadata><NS>SAS</NS><Flags>268436480</Flags><Options/>";
put "</DeleteMetadata>";
run ;
proc metadata in=&fname1 out=&fname2 verbose;run;
/* list the result */
data _null_;infile &fname2; input; list; run;
filename &fname1 clear;
filename &fname2 clear;
/**
* Check deletion
*/
%local isgone;
data _null_;
length type uri $256;
rc=metadata_resolve("omsobj:SASLibrary?@Id='&liburi'",type,uri);
call symputx('isgone',type,'l');
run;
%mp_abort(iftrue=(&isgone = SASLibrary)
,mac=&sysmacroname
,msg=%str(Library (&name) NOT deleted)
)
%put &sysmacroname: Library &name (&liburi) was successfully deleted;
%mend;

View File

@@ -21,8 +21,8 @@
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%if %length(&outds)>30 %then %do; %if %length(&outds)>30 %then %do;
%put %str(ERR)OR: Temp tables are created with the &outds prefix, which therefore %put %str(ERR)OR: Temp tables are created with the &outds prefix, which
needs to be 30 characters or less; therefore needs to be 30 characters or less;
%return; %return;
%end; %end;
%if %index(&outds,'.')>0 %then %do; %if %index(&outds,'.')>0 %then %do;

View File

@@ -39,8 +39,10 @@ data &outds (keep=directoryuri name directoryname directorydesc );
do while do while
(metadata_getnobj("omsobj:Directory?@Id contains '.'",__i,directoryuri)>0); (metadata_getnobj("omsobj:Directory?@Id contains '.'",__i,directoryuri)>0);
%end; %else %do; %end; %else %do;
do while do while(
(metadata_getnobj("omsobj:Directory?@DirectoryName='&path'",__i,directoryuri)>0); metadata_getnobj("omsobj:Directory?@DirectoryName='&path'",__i,directoryuri)
>0
);
%end; %end;
__rc1=metadata_getattr(directoryuri, "Name", name); __rc1=metadata_getattr(directoryuri, "Name", name);
__rc2=metadata_getattr(directoryuri, "DirectoryName", directoryname); __rc2=metadata_getattr(directoryuri, "DirectoryName", directoryname);

View File

@@ -0,0 +1,96 @@
/**
@file
@brief Returns all direct child members of a particular folder
@details Displays the children for a particular folder, in a similar fashion
to the viya counterpart (mv_getfoldermembers.sas)
Usage:
%mm_getfoldermembers(root=/, outds=rootfolders)
%mm_getfoldermembers(root=/User Folders/&sysuserid, outds=usercontent)
@param [in] root= the parent folder under which to return all contents
@param [out] outds= the dataset to create that contains the list of
directories
@param [in] mDebug= set to 1 to show debug messages in the log
<h4> Data Outputs </h4>
Example for `root=/`:
|metauri $17|metaname $256|metatype $32|
|---|---|---|
|A5XLSNXI.AA000001|Products |Folder|
|A5XLSNXI.AA000002|Shared Data |Folder|
|A5XLSNXI.AA000003|User Folders |Folder|
|A5XLSNXI.AA000004|System |Folder|
|A5XLSNXI.AA00003K|30.SASApps |Folder|
|A5XLSNXI.AA00006A|Public|Folder|
<h4> SAS Macros </h4>
@li mm_getfoldertree.sas
@li mf_getuniquefileref.sas
@li mf_getuniquelibref.sas
@version 9.4
@author Allan Bowe
**/
%macro mm_getfoldermembers(
root=
,outds=work.mm_getfoldertree
)/*/STORE SOURCE*/;
%if "&root" = "/" %then %do;
%local fname1 fname2 fname3;
%let fname1=%mf_getuniquefileref();
%let fname2=%mf_getuniquefileref();
%let fname3=%mf_getuniquefileref();
data _null_ ;
file &fname1 ;
put '<GetMetadataObjects>' ;
put '<Reposid>$METAREPOSITORY</Reposid>' ;
put '<Type>Tree</Type>' ;
put '<NS>SAS</NS>' ;
put '<Flags>388</Flags>' ;
put '<Options>' ;
put '<XMLSelect search="Tree[SoftwareComponents/SoftwareComponent'@;
put '[@Name=''BIP Service'']]"/>';
put '</Options>' ;
put '</GetMetadataObjects>' ;
run ;
proc metadata in=&fname1 out=&fname2 verbose;run;
/* create an XML map to read the response */
data _null_;
file &fname3;
put '<SXLEMAP version="1.2" name="SASFolders">';
put '<TABLE name="SASFolders">';
put '<TABLE-PATH syntax="XPath">//Objects/Tree</TABLE-PATH>';
put '<COLUMN name="metauri">><LENGTH>17</LENGTH>';
put '<PATH syntax="XPath">//Objects/Tree/@Id</PATH></COLUMN>';
put '<COLUMN name="metaname"><LENGTH>256</LENGTH>>';
put '<PATH syntax="XPath">//Objects/Tree/@Name</PATH></COLUMN>';
put '</TABLE></SXLEMAP>';
run;
%local libref1;
%let libref1=%mf_getuniquelibref();
libname &libref1 xml xmlfileref=&fname2 xmlmap=&fname3;
data &outds;
length metatype $32;
retain metatype 'Folder';
set &libref1..sasfolders;
run;
%end;
%else %do;
%mm_getfoldertree(root=&root, outds=&outds,depth=1)
data &outds;
set &outds(rename=(name=metaname publictype=metatype));
keep metaname metauri metatype;
run;
%end;
%mend;

View File

@@ -1,18 +1,21 @@
/** /**
@file mm_getfoldertree.sas @file
@brief Returns all folders / subfolder content for a particular root @brief Returns all folders / subfolder content for a particular root
@details Shows all members and SubTrees recursively for a particular root. @details Shows all members and SubTrees recursively for a particular root.
Note - for big sites, this returns a lot of data! So you may wish to reduce Note - for big sites, this returns a lot of data! So you may wish to reduce
the logging to speed up the process (see example below) the logging to speed up the process (see example below), OR - use mm_tree.sas
which uses proc metadata and is far more efficient.
Usage: Usage:
options ps=max nonotes nosource; options ps=max nonotes nosource;
%mm_getfoldertree(root=/My/Meta/Path, outds=iwantthisdataset) %mm_getfoldertree(root=/My/Meta/Path, outds=iwantthisdataset)
options notes source; options notes source;
@param root= the parent folder under which to return all contents @param [in] root= the parent folder under which to return all contents
@param outds= the dataset to create that contains the list of directories @param [out] outds= the dataset to create that contains the list of
@param mDebug= set to 1 to show debug messages in the log directories
@param [in] mDebug= set to 1 to show debug messages in the log
<h4> SAS Macros </h4> <h4> SAS Macros </h4>

View File

@@ -11,7 +11,8 @@
@param [in] user= the metadata user to return groups for. Leave blank for all @param [in] user= the metadata user to return groups for. Leave blank for all
groups. groups.
@param [in] repo= the metadata repository that contains the user/group information @param [in] repo= the metadata repository that contains the user/group
information
@param [in] mDebug= set to 1 to show debug messages in the log @param [in] mDebug= set to 1 to show debug messages in the log
@param [out] outds= the dataset to create that contains the list of groups @param [out] outds= the dataset to create that contains the list of groups
@@ -39,7 +40,8 @@
%&mD.put Executing mm_getGroups.sas; %&mD.put Executing mm_getGroups.sas;
%&mD.put _local_; %&mD.put _local_;
/* on some sites, user / group info is in a different metadata repo to the default */ /* on some sites, user / group info is in a different metadata repo to the
default */
%if &oldrepo ne &repo %then %do; %if &oldrepo ne &repo %then %do;
options metarepository=&repo; options metarepository=&repo;
%end; %end;

129
meta/mm_getlibmetadiffs.sas Normal file
View File

@@ -0,0 +1,129 @@
/**
@file
@brief Compares the metadata of a library with the physical tables
@details Creates a series of output tables that show the differences between
metadata and physical tables.
Each output can be created with an optional prefix.
Credit - Paul Homes
https://platformadmin.com/blogs/paul/2012/11/sas-proc-metalib-ods-output
Usage:
%* create (and assign) a library for testing purposes ;
%mm_createlibrary(
libname=My Temp Library,
libref=XXTEMPXX,
tree=/User Folders/&sysuserid,
directory=%sysfunc(pathname(work))
)
%* create some tables;
data work.table1 table2 table3;
a=1;b='two';c=3;
run;
%* register the tables;
proc metalib;
omr=(library="My Temp Library");
report(type=detail);
update_rule (delete);
run;
%* modify the tables;
proc sql;
drop table table3;
alter table table2 drop c;
alter table table2 add d num;
%* run the macro;
%mm_getlibmetadiffs(libname=My Temp Library)
%* delete the library ;
%mm_deletelibrary(name=My Temp Library)
The program will create four output tables, with the following structure (and
example data):
#### &prefix.added
|name:$32.|metaID:$17.|SAStabName:$32.|
|---|---|---|
|||DATA1|
#### &prefix.deleted
|name:$32.|metaID:$17.|SAStabName:$32.|
|---|---|---|
|TABLE3|A5XLSNXI.BK0001HO|TABLE3|
#### &prefix.updated
|tabName:$32.|tabMetaID:$17.|SAStabName:$32.|metaName:$32.|metaID:$17.|sasname:$32.|metaType:$16.|change:$64.|
|---|---|---|---|---|---|---|---|
|TABLE2|A5XLSNXI.BK0001HN|TABLE2|c|A5XLSNXI.BM000MA9|c|Column|Deleted|
||||d||d|Column|Added|
#### &prefix.meta
|Label1:$28.|cValue1:$1.|nValue1:D12.3|
|---|---|---|
|Total tables analyzed|4|4|
|Tables to be Updated|1|1|
|Tables to be Deleted|1|1|
|Tables to be Added|1|1|
|Tables matching data source|1|1|
|Tables not processed|0|0|
If you are interested in more functionality like this (checking the health of
SAS metadata and your SAS 9 environment) then do contact [Allan Bowe](
https://www.linkedin.com/in/allanbowe) for details of our SAS 9 Health Check
service.
Our system scan will perform hundreds of checks to identify common issues,
such as dangling metadata, embedded passwords, security issues and more.
@param [in] libname= the metadata name of the library to be compared
@param [out] outlib= The output library in which to store the output tables.
Default=WORK.
@param [out] prefix The prefix for the four tables created. Default=metadiff.
@version 9.3
@author Allan Bowe
**/
%macro mm_getlibmetadiffs(
libname= ,
prefix=metadiff,
outlib=work
)/*/STORE SOURCE*/;
/* create tempds */
data;run;
%local tempds;
%let tempds=&syslast;
/* save options */
proc optsave out=&tempds;
run;
options VALIDVARNAME=ANY VALIDMEMNAME=EXTEND;
ods output
factoid1=&outlib..&prefix.meta
updtab=&outlib..&prefix.updated
addtab=&outlib..&prefix.added
deltab=&outlib..&prefix.deleted
;
proc metalib;
omr=(library="&libname");
noexec;
report(type=detail);
update_rule (delete);
run;
ods output close;
/* restore options */
proc optload data=&tempds;
run;
%mend mm_getlibmetadiffs;

View File

@@ -46,7 +46,8 @@ filename sxlemap temp;
data _null_; data _null_;
file sxlemap; file sxlemap;
put '<SXLEMAP version="1.2" name="SASObjects"><TABLE name="SASObjects">'; put '<SXLEMAP version="1.2" name="SASObjects"><TABLE name="SASObjects">';
put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/&type</TABLE-PATH>"; put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/&type";
put "</TABLE-PATH>";
put '<COLUMN name="id">'; put '<COLUMN name="id">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/&type/@Id</PATH>"; put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/&type/@Id</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>";

View File

@@ -1,7 +1,8 @@
/** /**
@file mm_getpublictypes.sas @file mm_getpublictypes.sas
@brief Creates a dataset with all deployable public types @brief Creates a dataset with all deployable public types
@details More info: https://support.sas.com/documentation/cdl/en/bisag/65422/HTML/default/viewer.htm#n1nkrdzsq5iunln18bk2236istkb.htm @details More info:
https://support.sas.com/documentation/cdl/en/bisag/65422/HTML/default/viewer.htm#n1nkrdzsq5iunln18bk2236istkb.htm
Usage: Usage:

View File

@@ -44,61 +44,76 @@ filename sxlemap temp;
data _null_; data _null_;
file sxlemap; file sxlemap;
put '<SXLEMAP version="1.2" name="SASRepos"><TABLE name="SASRepos">'; put '<SXLEMAP version="1.2" name="SASRepos"><TABLE name="SASRepos">';
put "<TABLE-PATH syntax='XPath'>/GetRepositories/Repositories/Repository</TABLE-PATH>"; put "<TABLE-PATH syntax='XPath'>/GetRepositories/Repositories/Repository";
put "</TABLE-PATH>";
put '<COLUMN name="id">'; put '<COLUMN name="id">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Id</PATH>"; put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Id";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>";
put '</COLUMN>'; put '</COLUMN>';
put '<COLUMN name="name">'; put '<COLUMN name="name">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Name</PATH>"; put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Name";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>";
put '</COLUMN>'; put '</COLUMN>';
put '<COLUMN name="desc">'; put '<COLUMN name="desc">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Desc</PATH>"; put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Desc";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>";
put '</COLUMN>'; put '</COLUMN>';
put '<COLUMN name="DefaultNS">'; put '<COLUMN name="DefaultNS">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@DefaultNS</PATH>"; put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@DefaultNS</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>";
put '</COLUMN>'; put '</COLUMN>';
put '<COLUMN name="RepositoryType">'; put '<COLUMN name="RepositoryType">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@RepositoryType</PATH>"; put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@RepositoryType</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>20</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>20</LENGTH>";
put '</COLUMN>'; put '</COLUMN>';
put '<COLUMN name="RepositoryFormat">'; put '<COLUMN name="RepositoryFormat">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@RepositoryFormat</PATH>"; put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@RepositoryFormat</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>10</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>10</LENGTH>";
put '</COLUMN>'; put '</COLUMN>';
put '<COLUMN name="Access">'; put '<COLUMN name="Access">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Access</PATH>"; put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@Access</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>16</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>16</LENGTH>";
put '</COLUMN>'; put '</COLUMN>';
put '<COLUMN name="CurrentAccess">'; put '<COLUMN name="CurrentAccess">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@CurrentAccess</PATH>"; put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@CurrentAccess</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>16</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>16</LENGTH>";
put '</COLUMN>'; put '</COLUMN>';
put '<COLUMN name="PauseState">'; put '<COLUMN name="PauseState">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@PauseState</PATH>"; put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@PauseState</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>16</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>16</LENGTH>";
put '</COLUMN>'; put '</COLUMN>';
put '<COLUMN name="Path">'; put '<COLUMN name="Path">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Path</PATH>"; put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Path";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>256</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>256</LENGTH>";
put '</COLUMN>'; put '</COLUMN>';
put '<COLUMN name="Engine">'; put '<COLUMN name="Engine">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Engine</PATH>"; put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Engine";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>8</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>8</LENGTH>";
put '</COLUMN>'; put '</COLUMN>';
put '<COLUMN name="Options">'; put '<COLUMN name="Options">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Options</PATH>"; put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Options";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>32</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>32</LENGTH>";
put '</COLUMN>'; put '</COLUMN>';
put '<COLUMN name="MetadataCreated">'; put '<COLUMN name="MetadataCreated">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@MetadataCreated</PATH>"; put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@MetadataCreated</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>24</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>24</LENGTH>";
put '</COLUMN>'; put '</COLUMN>';
put '<COLUMN name="MetadataUpdated">'; put '<COLUMN name="MetadataUpdated">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@MetadataUpdated</PATH>"; put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@MetadataUpdated</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>24</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>24</LENGTH>";
put '</COLUMN>'; put '</COLUMN>';
put '</TABLE></SXLEMAP>'; put '</TABLE></SXLEMAP>';

View File

@@ -44,15 +44,19 @@ filename sxlemap temp;
data _null_; data _null_;
file sxlemap; file sxlemap;
put '<SXLEMAP version="1.2" name="roles"><TABLE name="roles">'; put '<SXLEMAP version="1.2" name="roles"><TABLE name="roles">';
put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup</TABLE-PATH>"; put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup";
put "</TABLE-PATH>";
put '<COLUMN name="roleuri">'; put '<COLUMN name="roleuri">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup/@Id</PATH>"; put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup/@Id";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>32</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>32</LENGTH>";
put '</COLUMN><COLUMN name="rolename">'; put '</COLUMN><COLUMN name="rolename">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup/@Name</PATH>"; put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup/@Name";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>256</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>256</LENGTH>";
put '</COLUMN><COLUMN name="roledesc">'; put '</COLUMN><COLUMN name="roledesc">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup/@Desc</PATH>"; put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup/@Desc";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>500</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>500</LENGTH>";
put '</COLUMN></TABLE></SXLEMAP>'; put '</COLUMN></TABLE></SXLEMAP>';
run; run;

View File

@@ -23,9 +23,10 @@
combine with the <code>tree=</code> parameter. combine with the <code>tree=</code> parameter.
@param outds= the dataset to create that contains the list of stps. @param outds= the dataset to create that contains the list of stps.
@param mDebug= set to 1 to show debug messages in the log @param mDebug= set to 1 to show debug messages in the log
@param showDesc= provide a non blank value to return stored process descriptions @param showDesc= provide a non blank value to return stored process
@param showUsageVersion= provide a non blank value to return the UsageVersion. This descriptions
is either 1000000 (type 1, 9.2) or 2000000 (type2, 9.3 onwards). @param showUsageVersion= provide a non blank value to return the UsageVersion.
This is either 1000000 (type 1, 9.2) or 2000000 (type2, 9.3 onwards).
@returns outds dataset containing the following columns @returns outds dataset containing the following columns
- stpuri - stpuri

View File

@@ -1,7 +1,7 @@
/** /**
@file mm_gettableid.sas @file mm_gettableid.sas
@brief Get the metadata id for a particular table @brief Get the metadata id for a particular table
@details Provide a libref and table name to return the corresponding metadata id @details Provide a libref and table name to return the corresponding metadata
in an output datataset. in an output datataset.
Usage: Usage:

View File

@@ -48,7 +48,8 @@ filename sxlemap temp;
data _null_; data _null_;
file sxlemap; file sxlemap;
put '<SXLEMAP version="1.2" name="SASObjects"><TABLE name="SASObjects">'; put '<SXLEMAP version="1.2" name="SASObjects"><TABLE name="SASObjects">';
put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/Person</TABLE-PATH>"; put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/Person";
put "</TABLE-PATH>";
put '<COLUMN name="uri">'; put '<COLUMN name="uri">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/Person/@Id</PATH>"; put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/Person/@Id</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>32</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>32</LENGTH>";

View File

@@ -12,7 +12,8 @@
Usage: Usage:
%* import the macros (or make them available some other way); %* import the macros (or make them available some other way);
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;
%* create sample text file as input to the macro; %* create sample text file as input to the macro;
@@ -92,7 +93,8 @@
%let port=%sysfunc(getoption(metaport)); %let port=%sysfunc(getoption(metaport));
%let platform_object_path=%mf_loc(POF); %let platform_object_path=%mf_loc(POF);
%let connx_string=%str(-host &host -port &port -user &mmxuser -password &mmxpass); %let connx_string=%str(-host &host -port &port -user &mmxuser %trim(
)-password &mmxpass);
%mm_tree(root=%str(&metaloc) ,types=EXPORTABLE ,outds=exportable) %mm_tree(root=%str(&metaloc) ,types=EXPORTABLE ,outds=exportable)

View File

@@ -15,7 +15,8 @@
Usage: Usage:
%* load macros; %* load macros;
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;
%* export everything; %* export everything;
@@ -104,7 +105,8 @@ filename sxlemap temp;
data _null_; data _null_;
file sxlemap; file sxlemap;
put '<SXLEMAP version="1.2" name="SASObjects"><TABLE name="SASObjects">'; put '<SXLEMAP version="1.2" name="SASObjects"><TABLE name="SASObjects">';
put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/Tree</TABLE-PATH>"; put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/Tree";
put "</TABLE-PATH>";
put '<COLUMN name="pathuri">'; put '<COLUMN name="pathuri">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/Tree/@Id</PATH>"; put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/Tree/@Id</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>64</LENGTH>"; put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>64</LENGTH>";

View File

@@ -1,5 +1,5 @@
/** /**
@file mm_updatestpservertype.sas @file
@brief Updates a type 2 stored process to run on STP or WKS context @brief Updates a type 2 stored process to run on STP or WKS context
@details Only works on Type 2 (9.3 compatible) STPs @details Only works on Type 2 (9.3 compatible) STPs
@@ -8,11 +8,10 @@
%mm_updatestpservertype(target=/some/meta/path/myStoredProcess %mm_updatestpservertype(target=/some/meta/path/myStoredProcess
,type=WKS) ,type=WKS)
<h4> SAS Macros </h4>
@param target= full path to the STP being deleted @param target= full path to the STP being deleted
@param type= Either WKS or STP depending on whether Workspace or Stored Process @param type= Either WKS or STP depending on whether Workspace or
type required Stored Process type required
@version 9.4 @version 9.4
@author Allan Bowe @author Allan Bowe
@@ -52,7 +51,8 @@ data _null_;
n+1; n+1;
rc=metadata_getattr(uri,"Name",name); rc=metadata_getattr(uri,"Name",name);
if name='Stored Process' then do; if name='Stored Process' then do;
rc = METADATA_SETATTR(uri,'StoredText','<?xml version="1.0" encoding="UTF-8"?>' rc = METADATA_SETATTR(uri,'StoredText'
,'<?xml version="1.0" encoding="UTF-8"?>'
!!'<StoredProcess><ServerContext LogicalServerType="'!!"&newtype" !!'<StoredProcess><ServerContext LogicalServerType="'!!"&newtype"
!!'" OtherAllowed="false"/><ResultCapabilities Package="false" ' !!'" OtherAllowed="false"/><ResultCapabilities Package="false" '
!!' Streaming="true"/><OutputParameters/></StoredProcess>'); !!' Streaming="true"/><OutputParameters/></StoredProcess>');

View File

@@ -9,28 +9,37 @@
%mm_updatestpsourcecode(stp=/my/metadata/path/mystpname %mm_updatestpsourcecode(stp=/my/metadata/path/mystpname
,stpcode="/file/system/source.sas") ,stpcode="/file/system/source.sas")
@param [in] stp= the BIP Tree folder path plus Stored Process Name
@param stp= the BIP Tree folder path plus Stored Process Name @param [in] stpcode= the source file (or fileref) containing the SAS code to load
@param stpcode= the source file (or fileref) containing the SAS code to load
into the stp. For multiple files, they should simply be concatenated first. into the stp. For multiple files, they should simply be concatenated first.
@param minify= set to YES in order to strip comments, blank lines, and CRLFs. @param [in] minify= set to YES in order to strip comments, blank lines, and CRLFs.
@param frefin= change default inref if it clashes with an existing one @param frefin= deprecated - a unique fileref is now always used
@param frefout= change default outref if it clashes with an existing one @param frefout= deprecated - a unique fileref is now always used
@param mDebug= set to 1 to show debug messages in the log @param mDebug= set to 1 to show debug messages in the log
@version 9.3 @version 9.3
@author Allan Bowe @author Allan Bowe
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
**/ **/
%macro mm_updatestpsourcecode(stp= %macro mm_updatestpsourcecode(stp=
,stpcode= ,stpcode=
,minify=NO ,minify=NO
,mdebug=0
/* deprecated */
,frefin=inmeta ,frefin=inmeta
,frefout=outmeta ,frefout=outmeta
,mdebug=0
); );
%if &frefin ne inmeta or &frefout ne outmeta %then %do;
%put %str(WARN)ING: the frefin and frefout parameters will be deprecated in
an upcoming release.;
%end;
/* first, check if STP exists */ /* first, check if STP exists */
%local tsuri; %local tsuri;
%let tsuri=stopifempty ; %let tsuri=stopifempty ;
@@ -68,7 +77,9 @@ run;
%return; %return;
%end; %end;
filename &frefin temp lrecl=32767; %local frefin frefout;
%let frefin=%mf_getuniquefileref();
%let frefout=%mf_getuniquefileref();
/* write header XML */ /* write header XML */
data _null_; data _null_;
@@ -81,7 +92,7 @@ run;
/* write contents */ /* write contents */
%if %length(&stpcode)>2 %then %do; %if %length(&stpcode)>2 %then %do;
data _null_; data _null_;
file &frefin mod; file &frefin lrecl=32767 mod;
infile &stpcode lrecl=32767; infile &stpcode lrecl=32767;
length outstr $32767; length outstr $32767;
input outstr ; input outstr ;
@@ -110,9 +121,6 @@ data _null_;
</UpdateMetadata>"; </UpdateMetadata>";
run; run;
filename &frefout temp;
proc metadata in= &frefin out=&frefout; proc metadata in= &frefin out=&frefout;
run; run;
@@ -124,5 +132,9 @@ run;
put _infile_; put _infile_;
run; run;
%end; %end;
%else %do;
filename &frefin clear;
filename &frefout clear;
%end;
%mend; %mend;

View File

@@ -20,7 +20,8 @@ Usage:
run; run;
filename outref "%sysfunc(pathname(work))"; filename outref "%sysfunc(pathname(work))";
%mmx_spkexport(metaloc=%str(/30.Projects/3001.Internal/300115.DataController/dc1) %mmx_spkexport(
metaloc=%str(/30.Projects/3001.Internal/300115.DataController/dc1)
,secureref=tmp ,secureref=tmp
,outspkpath=%str(/tmp) ,outspkpath=%str(/tmp)
) )
@@ -56,7 +57,8 @@ Usage:
/* get creds */ /* get creds */
%inc &secureref/nosource; %inc &secureref/nosource;
%let connx_string=%str(-host &host -port &port -user '&mmxuser' -password '&mmxpass'); %let connx_string=
%str(-host &host -port &port -user '&mmxuser' -password '&mmxpass');
%mm_tree(root=%str(&metaloc) ,types=EXPORTABLE ,outds=exportable) %mm_tree(root=%str(&metaloc) ,types=EXPORTABLE ,outds=exportable)

1691
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "@sasjs/core", "name": "@sasjs/core",
"description": "SAS Macro Library for Application Development", "description": "Production Ready Macros for SAS Application Developers",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [
"SAS", "SAS",
@@ -10,13 +10,33 @@
"author": "Allan Bowe <support@macropeople.com>", "author": "Allan Bowe <support@macropeople.com>",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/sasjs/core" "url": "git+https://github.com/sasjs/core.git"
}, },
"release": { "release": {
"branches": ["main"] "branches": [
"main"
]
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
"devDependencies": {} "bugs": {
"url": "https://github.com/sasjs/core/issues"
},
"homepage": "https://github.com/sasjs/core#readme",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"docs": "sasjs doc && ./sasjs/utils/build.sh"
},
"devDependencies": {
"@sasjs/cli": "^2.14.2",
"ghooks": "^2.0.4"
},
"config": {
"ghooks": {
"pre-commit": "sasjs lint"
}
}
} }

View File

@@ -1,5 +1,5 @@
ALPHABETICAL_INDEX = NO ALPHABETICAL_INDEX = NO
DISABLE_INDEX = YES DISABLE_INDEX = NO
ENABLE_PREPROCESSING = NO ENABLE_PREPROCESSING = NO
EXTENSION_MAPPING = sas=Java ddl=Java EXTENSION_MAPPING = sas=Java ddl=Java
EXTRACT_LOCAL_CLASSES = NO EXTRACT_LOCAL_CLASSES = NO
@@ -13,18 +13,21 @@ HIDE_IN_BODY_DOCS = YES
HIDE_SCOPE_NAMES = YES HIDE_SCOPE_NAMES = YES
HIDE_UNDOC_CLASSES = YES HIDE_UNDOC_CLASSES = YES
HIDE_UNDOC_MEMBERS = YES HIDE_UNDOC_MEMBERS = YES
HTML_OUTPUT = doxy HTML_OUTPUT = $(DOXY_HTML_OUTPUT)
HTML_HEADER = ./doxy/new_header.html HTML_HEADER = $(HTML_HEADER)
HTML_FOOTER = ./doxy/new_footer.html HTML_EXTRA_FILES = $(HTML_EXTRA_FILES)
HTML_EXTRA_STYLESHEET = ./doxy/new_stylesheet.css HTML_FOOTER = $(HTML_FOOTER)
HTML_EXTRA_STYLESHEET = $(HTML_EXTRA_STYLESHEET)
INHERIT_DOCS = NO INHERIT_DOCS = NO
INLINE_INFO = NO INLINE_INFO = NO
INPUT = base meta metax viya lua INPUT = $(DOXY_INPUT)
LAYOUT_FILE = ./doxy/DoxygenLayout.xml INPUT += main.dox
LAYOUT_FILE = $(LAYOUT_FILE)
USE_MDFILE_AS_MAINPAGE = README.md
MAX_INITIALIZER_LINES = 0 MAX_INITIALIZER_LINES = 0
PROJECT_NAME = Macro Core PROJECT_NAME = $(PROJECT_NAME)
PROJECT_LOGO = doxy/Macro_core_website_1.png PROJECT_LOGO = $(PROJECT_LOGO)
PROJECT_BRIEF = "Production Ready Macros for SAS Application Developers" PROJECT_BRIEF = $(PROJECT_BRIEF)
RECURSIVE = YES RECURSIVE = YES
REPEAT_BRIEF = NO REPEAT_BRIEF = NO
SHOW_NAMESPACES = NO SHOW_NAMESPACES = NO

View File

@@ -2,7 +2,7 @@
<!-- Generated by doxygen 1.8.14 --> <!-- Generated by doxygen 1.8.14 -->
<!-- Navigation index tabs for HTML output --> <!-- Navigation index tabs for HTML output -->
<navindex> <navindex>
<tab type="mainpage" visible="no" title=""/> <tab type="mainpage" visible="yes" title="Home"/>
<tab type="pages" visible="no" title="" intro=""/> <tab type="pages" visible="no" title="" intro=""/>
<tab type="modules" visible="no" title="" intro=""/> <tab type="modules" visible="no" title="" intro=""/>
<tab type="namespaces" visible="no" title=""> <tab type="namespaces" visible="no" title="">

View File

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

26
sasjs/doxy/doxygen.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

BIN
sasjs/doxy/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
sasjs/doxy/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -0,0 +1,33 @@
<!-- HTML footer for doxygen 1.8.17-->
<!-- start footer part -->
<!--BEGIN GENERATE_TREEVIEW-->
<div id="nav-path" class="navpath">
<!-- id is needed for treeview function! -->
<ul>
$navpath
<li class="footer">
$generatedby
<a href="https://www.doxygen.org/index.html">
<img class="footer" src="$relpath^doxygen.svg" alt="doxygen"
/></a>
$doxygenversion
</li>
<li>
<i> For more information visit the </i>
<a href="https://github.com/sasjs/core">Macro Core library</a>.
</li>
</ul>
</div>
<!--END GENERATE_TREEVIEW-->
<!--BEGIN !GENERATE_TREEVIEW-->
<hr class="footer" />
<address class="footer">
<small>
$generatedby &#160;<a href="http://www.doxygen.org/index.html">
<img class="footer" src="$relpath^doxygen.svg" alt="doxygen" />
</a>
$doxygenversion
</small>
</address>
<!--END !GENERATE_TREEVIEW-->

View File

@@ -0,0 +1,67 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- HTML header for doxygen 1.8.17-->
<html xmlns="https://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=9" />
<meta property="og:type" content="website">
<meta name="author" content="Allan Bowe">
<meta name="generator" content="Doxygen $doxygenversion" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!--BEGIN PROJECT_NAME-->
<meta name="description" content="$projectbrief" />
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<title>$title</title>
<!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview $search $mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
<link rel="shortcut icon" href="$relpath^favicon.ico" type="image/x-icon" />
$extrastylesheet
</head>
<body>
<div id="top">
<!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo">
<a href="$relpath^"
><img alt="Logo" src="$relpath^$projectlogo"
/></a>
</td>
<!--END PROJECT_LOGO-->
<td id="projectalign" style="padding-left: 0.5em">
<div id="projectbrief">
Production Ready Macros for SAS Application Developers<br />
<a href="https://github.com/sasjs/core">
https://github.com/sasjs/core
</a>
</div>
</td>
</tr>
</table>
</td>
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<td>$searchbox</td>
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->
</div>
</body>
</html>

View File

@@ -0,0 +1,5 @@
#projectlogo img
{
border: 0px none;
max-height:70px
}

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

13
sasjs/sasjsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
{
"$schema": "https://cli.sasjs.io/sasjsconfig-schema.json",
"macroFolders": ["base", "meta", "metax", "viya", "lua"],
"docConfig":{
"displayMacroCore": false,
"enableLineage": false,
"doxyContent": {
"favIcon": "runningman.jpg",
"logo": "Macro_core_website_1.png",
"readMe": "../../README.md"
}
}
}

22
sasjs/utils/build.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
####################################################################
# PROJECT: Macro Core Docs Build #
# To execute, use the npm command (npm run docs) #
####################################################################
# refresh github pages site
rm -rf sasjsbuild/docsite
git clone git@github.com:sasjs/core.github.io.git sasjsbuild/docsite
rm -rf sasjsbuild/docsite/*
mv sasjsbuild/docs/* sasjsbuild/docsite/
cd sasjsbuild/docsite/
echo 'core.sasjs.io' > CNAME
git add .
git commit -m "build.sh build on $(date +%F:%H:%M:%S)"
git push
npx sitemap-generator-cli https://core.sasjs.io
git add .
git commit -m "adding sitemap"
git push
echo "check it out: https://sasjs.github.io/core.github.io/files.html"

View File

@@ -4,7 +4,6 @@
@details Expects oauth token in a global macro variable (default @details Expects oauth token in a global macro variable (default
ACCESS_TOKEN). ACCESS_TOKEN).
options mprint;
%mv_createfolder(path=/Public) %mv_createfolder(path=/Public)

308
viya/mv_createjob.sas Normal file
View File

@@ -0,0 +1,308 @@
/**
@file
@brief Creates a Viya Job
@details
Code is passed in as one or more filerefs.
%* Step 1 - compile macros ;
filename mc url
"https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
%* Step 2 - Create some SAS code and add it to a job;
filename ft15f001 temp;
parmcards4;
data some_code;
set sashelp.class;
run;
;;;;
%mv_createjob(path=/Public/app/sasjstemp/jobs/myjobs,name=myjob)
The path to the job will then be shown in the log, eg as follows:
![viya job location](https://i.imgur.com/XRUDHgA.png)
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mv_createfolder.sas
@li mf_getuniquelibref.sas
@li mf_getuniquefileref.sas
@li mf_getplatform.sas
@li mf_isblank.sas
@li mv_deletejes.sas
@param path= The full path (on SAS Drive) where the job will be created
@param name= The name of the job
@param desc= The description of the job
@param precode= Space separated list of filerefs, pointing to the code that
needs to be attached to the beginning of the job
@param code= Fileref(s) of the actual code to be added
@param access_token_var= The global macro variable to contain the access token
@param grant_type= valid values are "password" or "authorization_code"
(unquoted). The default is authorization_code.
@param replace= select NO to avoid replacing any existing job in that location
@param contextname= Choose a specific context on which to run the Job. Leave
blank to use the default context. From Viya 3.5 it is possible to configure
a shared context - see
https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5pyhn1wg3ja0drdl6h.htm&docsetVersion=3.5&locale=en
@version VIYA V.03.04
@author [Allan Bowe](https://www.linkedin.com/in/allanbowe)
**/
%macro mv_createjob(path=
,name=
,desc=Created by the mv_createjob.sas macro
,precode=
,code=ft15f001
,access_token_var=ACCESS_TOKEN
,grant_type=sas_services
,replace=YES
,debug=0
,contextname=
);
%local oauth_bearer;
%if &grant_type=detect %then %do;
%if %symexist(&access_token_var) %then %let grant_type=authorization_code;
%else %let grant_type=sas_services;
%end;
%if &grant_type=sas_services %then %do;
%let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=;
%end;
%put &sysmacroname: grant_type=&grant_type;
/* initial validation checking */
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services
)
,mac=&sysmacroname
,msg=%str(Invalid value for grant_type: &grant_type)
)
%mp_abort(iftrue=(%mf_isblank(&path)=1)
,mac=&sysmacroname
,msg=%str(path value must be provided)
)
%mp_abort(iftrue=(%length(&path)=1)
,mac=&sysmacroname
,msg=%str(path value must be provided)
)
%mp_abort(iftrue=(%mf_isblank(&name)=1)
,mac=&sysmacroname
,msg=%str(name value must be provided)
)
options noquotelenmax;
* remove any trailing slash ;
%if "%substr(&path,%length(&path),1)" = "/" %then
%let path=%substr(&path,1,%length(&path)-1);
/* ensure folder exists */
%put &sysmacroname: Path &path being checked / created;
%mv_createfolder(path=&path)
%local base_uri; /* location of rest apis */
%let base_uri=%mf_getplatform(VIYARESTAPI);
/* fetching folder details for provided path */
%local fname1;
%let fname1=%mf_getuniquefileref();
proc http method='GET' out=&fname1 &oauth_bearer
url="&base_uri/folders/folders/@item?path=&path";
%if &grant_type=authorization_code %then %do;
headers "Authorization"="Bearer &&&access_token_var";
%end;
run;
%if &debug %then %do;
data _null_;
infile &fname1;
input;
putlog _infile_;
run;
%end;
%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200)
,mac=&sysmacroname
,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
)
/* path exists. Grab follow on link to check members */
%local libref1;
%let libref1=%mf_getuniquelibref();
libname &libref1 JSON fileref=&fname1;
data _null_;
set &libref1..links;
if rel='members' then call symputx('membercheck',quote("&base_uri"!!trim(href)),'l');
else if rel='self' then call symputx('parentFolderUri',href,'l');
run;
data _null_;
set &libref1..root;
call symputx('folderid',id,'l');
run;
%local fname2;
%let fname2=%mf_getuniquefileref();
proc http method='GET'
out=&fname2
&oauth_bearer
url=%unquote(%superq(membercheck));
headers
%if &grant_type=authorization_code %then %do;
"Authorization"="Bearer &&&access_token_var"
%end;
'Accept'='application/vnd.sas.collection+json'
'Accept-Language'='string';
%if &debug=1 %then %do;
debug level = 3;
%end;
run;
/*data _null_;infile &fname2;input;putlog _infile_;run;*/
%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200)
,mac=&sysmacroname
,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
)
%if %upcase(&replace)=YES %then %do;
%mv_deletejes(path=&path, name=&name)
%end;
%else %do;
/* check that job does not already exist in that folder */
%local libref2;
%let libref2=%mf_getuniquelibref();
libname &libref2 JSON fileref=&fname2;
%local exists; %let exists=0;
data _null_;
set &libref2..items;
if contenttype='jobDefinition' and upcase(name)="%upcase(&name)" then
call symputx('exists',1,'l');
run;
%mp_abort(iftrue=(&exists=1)
,mac=&sysmacroname
,msg=%str(Job &name already exists in &path)
)
libname &libref2 clear;
%end;
/* set up the body of the request to create the service */
%local fname3;
%let fname3=%mf_getuniquefileref();
data _null_;
file &fname3 TERMSTR=' ';
length string $32767;
string=cats('{"version": 0,"name":"'
,"&name"
,'","type":"Compute","parameters":[{"name":"_addjesbeginendmacros"'
,',"type":"CHARACTER","defaultValue":"false"}');
context=quote(cats(symget('contextname')));
if context ne '""' then do;
string=cats(string,',{"version": 1,"name": "_contextName","defaultValue":'
,context,',"type":"CHARACTER","label":"Context Name","required": false}');
end;
string=cats(string,'],"code":"');
put string;
run;
/* insert the code, escaping double quotes and carriage returns */
%local x fref freflist;
%let freflist= &precode &code ;
%do x=1 %to %sysfunc(countw(&freflist));
%let fref=%scan(&freflist,&x);
%put &sysmacroname: adding &fref;
data _null_;
length filein 8 fileid 8;
filein = fopen("&fref","I",1,"B");
fileid = fopen("&fname3","A",1,"B");
rec = "20"x;
do while(fread(filein)=0);
rc = fget(filein,rec,1);
if rec='"' then do; /* DOUBLE QUOTE */
rc =fput(fileid,'\');rc =fwrite(fileid);
rc =fput(fileid,'"');rc =fwrite(fileid);
end;
else if rec='0A'x then do; /* LF */
rc =fput(fileid,'\');rc =fwrite(fileid);
rc =fput(fileid,'n');rc =fwrite(fileid);
end;
else if rec='0D'x then do; /* CR */
rc =fput(fileid,'\');rc =fwrite(fileid);
rc =fput(fileid,'r');rc =fwrite(fileid);
end;
else if rec='09'x then do; /* TAB */
rc =fput(fileid,'\');rc =fwrite(fileid);
rc =fput(fileid,'t');rc =fwrite(fileid);
end;
else if rec='5C'x then do; /* BACKSLASH */
rc =fput(fileid,'\');rc =fwrite(fileid);
rc =fput(fileid,'\');rc =fwrite(fileid);
end;
else do;
rc =fput(fileid,rec);
rc =fwrite(fileid);
end;
end;
rc=fclose(filein);
rc=fclose(fileid);
run;
%end;
/* finish off the body of the code file loaded to JES */
data _null_;
file &fname3 mod TERMSTR=' ';
put '"}';
run;
/* now we can create the job!! */
%local fname4;
%let fname4=%mf_getuniquefileref();
proc http method='POST'
in=&fname3
out=&fname4
&oauth_bearer
url="&base_uri/jobDefinitions/definitions?parentFolderUri=&parentFolderUri";
headers 'Content-Type'='application/vnd.sas.job.definition+json'
%if &grant_type=authorization_code %then %do;
"Authorization"="Bearer &&&access_token_var"
%end;
"Accept"="application/vnd.sas.job.definition+json";
%if &debug=1 %then %do;
debug level = 3;
%end;
run;
/*data _null_;infile &fname4;input;putlog _infile_;run;*/
%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 201)
,mac=&sysmacroname
,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
)
/* clear refs */
filename &fname1 clear;
filename &fname2 clear;
filename &fname3 clear;
filename &fname4 clear;
libname &libref1 clear;
/* get the url so we can give a helpful log message */
%local url;
data _null_;
if symexist('_baseurl') then do;
url=symget('_baseurl');
if subpad(url,length(url)-9,9)='SASStudio'
then url=substr(url,1,length(url)-11);
else url="&systcpiphostname";
end;
else url="&systcpiphostname";
call symputx('url',url);
run;
%put &sysmacroname: Job &name successfully created in &path;
%put &sysmacroname:;
%put &sysmacroname: Check it out here:;
%put &sysmacroname:;%put;
%put &url/SASJobExecution?_PROGRAM=&path/&name;%put;
%put &sysmacroname:;
%put &sysmacroname:;
%mend;

View File

@@ -1,11 +1,12 @@
/** /**
@file mv_createwebservice.sas @file
@brief Creates a JobExecution web service if it doesn't already exist @brief Creates a JobExecution web service if it doesn't already exist
@details @details
Code is passed in as one or more filerefs. Code is passed in as one or more filerefs.
%* Step 1 - compile macros ; %* Step 1 - compile macros ;
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;
%* Step 2 - Create some code and add it to a web service; %* Step 2 - Create some code and add it to a web service;
@@ -52,7 +53,8 @@
adapter, add a (different) fileref here. adapter, add a (different) fileref here.
@param contextname= Choose a specific context on which to run the Job. Leave @param contextname= Choose a specific context on which to run the Job. Leave
blank to use the default context. From Viya 3.5 it is possible to configure blank to use the default context. From Viya 3.5 it is possible to configure
a shared context - see https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5pyhn1wg3ja0drdl6h.htm&docsetVersion=3.5&locale=en a shared context - see
https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5pyhn1wg3ja0drdl6h.htm&docsetVersion=3.5&locale=en
@version VIYA V.03.04 @version VIYA V.03.04
@author Allan Bowe, source: https://github.com/sasjs/core @author Allan Bowe, source: https://github.com/sasjs/core
@@ -256,7 +258,8 @@ data _null_;
put ' %end; '; put ' %end; ';
put ' data _null_;file &jref mod ; '; put ' data _null_;file &jref mod ; ';
put ' put "["; call symputx(''cols'',0,''l''); '; put ' put "["; call symputx(''cols'',0,''l''); ';
put ' proc sort data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) '; put ' proc sort ';
put ' data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) ';
put ' out=_data_; '; put ' out=_data_; ';
put ' by varnum; '; put ' by varnum; ';
put ' '; put ' ';
@@ -295,7 +298,8 @@ data _null_;
put ' %end; '; put ' %end; ';
put ' %end; '; put ' %end; ';
put ' run; '; put ' run; ';
put ' /* write to temp loc to avoid _webout truncation - https://support.sas.com/kb/49/325.html */ '; put ' /* write to temp loc to avoid _webout truncation ';
put ' - https://support.sas.com/kb/49/325.html */ ';
put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; '; put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; '; put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; ';
put ' set &tempds; '; put ' set &tempds; ';
@@ -410,7 +414,8 @@ data _null_;
put ' if _n_=1 then call symputx(''input_statement'',_infile_); '; put ' if _n_=1 then call symputx(''input_statement'',_infile_); ';
put ' list; '; put ' list; ';
put ' data &table; '; put ' data &table; ';
put ' infile "%sysfunc(pathname(work))/&table..csv" firstobs=2 dsd termstr=crlf; '; put ' infile "%sysfunc(pathname(work))/&table..csv" firstobs=2 dsd ';
put ' termstr=crlf; ';
put ' input &input_statement; '; put ' input &input_statement; ';
put ' run; '; put ' run; ';
put ' %end; '; put ' %end; ';
@@ -451,7 +456,8 @@ data _null_;
put ' '; put ' ';
put ' /* setup temp ref */ '; put ' /* setup temp ref */ ';
put ' %if %upcase(&fref) ne _WEBOUT %then %do; '; put ' %if %upcase(&fref) ne _WEBOUT %then %do; ';
put ' filename &fref temp lrecl=999999 permission=''A::u::rwx,A::g::rw-,A::o::---'' mod; '; put ' filename &fref temp lrecl=999999 permission=''A::u::rwx,A::g::rw-,A::o::---'' ';
put ' mod; ';
put ' %end; '; put ' %end; ';
put ' '; put ' ';
put ' /* setup json */ '; put ' /* setup json */ ';
@@ -566,23 +572,23 @@ run;
rec = "20"x; rec = "20"x;
do while(fread(filein)=0); do while(fread(filein)=0);
rc = fget(filein,rec,1); rc = fget(filein,rec,1);
if rec='"' then do; if rec='"' then do; /* DOUBLE QUOTE */
rc =fput(fileid,'\');rc =fwrite(fileid); rc =fput(fileid,'\');rc =fwrite(fileid);
rc =fput(fileid,'"');rc =fwrite(fileid); rc =fput(fileid,'"');rc =fwrite(fileid);
end; end;
else if rec='0A'x then do; else if rec='0A'x then do; /* LF */
rc =fput(fileid,'\');rc =fwrite(fileid);
rc =fput(fileid,'r');rc =fwrite(fileid);
end;
else if rec='0D'x then do;
rc =fput(fileid,'\');rc =fwrite(fileid); rc =fput(fileid,'\');rc =fwrite(fileid);
rc =fput(fileid,'n');rc =fwrite(fileid); rc =fput(fileid,'n');rc =fwrite(fileid);
end; end;
else if rec='09'x then do; else if rec='0D'x then do; /* CR */
rc =fput(fileid,'\');rc =fwrite(fileid);
rc =fput(fileid,'r');rc =fwrite(fileid);
end;
else if rec='09'x then do; /* TAB */
rc =fput(fileid,'\');rc =fwrite(fileid); rc =fput(fileid,'\');rc =fwrite(fileid);
rc =fput(fileid,'t');rc =fwrite(fileid); rc =fput(fileid,'t');rc =fwrite(fileid);
end; end;
else if rec='5C'x then do; else if rec='5C'x then do; /* BACKSLASH */
rc =fput(fileid,'\');rc =fwrite(fileid); rc =fput(fileid,'\');rc =fwrite(fileid);
rc =fput(fileid,'\');rc =fwrite(fileid); rc =fput(fileid,'\');rc =fwrite(fileid);
end; end;

View File

@@ -46,7 +46,7 @@
%let oauth_bearer=oauth_bearer=sas_services; %let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=; %let &access_token_var=;
%end; %end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services and &grant_type ne sas_services
) )

View File

@@ -4,7 +4,6 @@
@details If not running in Studo 5 +, will expect an oauth token in a global @details If not running in Studo 5 +, will expect an oauth token in a global
macro variable (default ACCESS_TOKEN). macro variable (default ACCESS_TOKEN).
options mprint;
%mv_createfolder(path=/Public/test/blah) %mv_createfolder(path=/Public/test/blah)
%mv_deleteviyafolder(path=/Public/test) %mv_deleteviyafolder(path=/Public/test)

View File

@@ -40,7 +40,7 @@
%let oauth_bearer=oauth_bearer=sas_services; %let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=; %let &access_token_var=;
%end; %end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services and &grant_type ne sas_services
) )

View File

@@ -1,31 +1,20 @@
/** /**
@file mv_getgroups.sas @file mv_getgroups.sas
@brief Creates a dataset with a list of viya groups @brief Creates a dataset with a list of viya groups
@details First, be sure you have an access token (which requires an app token). @details First, load the macros:
Using the macros here:
filename mc url filename mc url
"https://raw.githubusercontent.com/sasjs/core/main/all.sas"; "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc; %inc mc;
An administrator needs to set you up with an access code: Next, execute:
%mv_registerclient(outds=client) %mv_getgroups(outds=work.groups)
Navigate to the url from the log (opting in to the groups) and paste the @param [in] access_token_var= The global macro variable to contain the access token
access code below: @param [in] grant_type= valid values are "password" or "authorization_code" (unquoted).
%mv_tokenauth(inds=client,code=wKDZYTEPK6)
Now we can run the macro!
%mv_getgroups()
@param access_token_var= The global macro variable to contain the access token
@param grant_type= valid values are "password" or "authorization_code" (unquoted).
The default is authorization_code. The default is authorization_code.
@param outds= The library.dataset to be created that contains the list of groups @param [out] outds= The library.dataset to be created that contains the list of groups
@version VIYA V.03.04 @version VIYA V.03.04
@@ -52,7 +41,7 @@
%let oauth_bearer=oauth_bearer=sas_services; %let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=; %let &access_token_var=;
%end; %end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services and &grant_type ne sas_services
) )

View File

@@ -49,7 +49,6 @@
%let oauth_bearer=oauth_bearer=sas_services; %let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=; %let &access_token_var=;
%end; %end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services and &grant_type ne sas_services
) )
@@ -114,7 +113,7 @@ run;
%let fname3=%mf_getuniquefileref(); %let fname3=%mf_getuniquefileref();
%let fpath1=%sysfunc(pathname(&fname1)); %let fpath1=%sysfunc(pathname(&fname1));
%let fpath2=%sysfunc(pathname(&fname2)); %let fpath2=%sysfunc(pathname(&fname2));
%let fpath3=%sysfunc(pathname(&fname2)); %let fpath3=%sysfunc(pathname(&fname3));
/* compile the lua JSON module */ /* compile the lua JSON module */
%ml_json() %ml_json()
@@ -147,4 +146,5 @@ data _null_;
run; run;
filename &fname1 clear; filename &fname1 clear;
filename &fname2 clear; filename &fname2 clear;
filename &fname3 clear;
%mend; %mend;

268
viya/mv_getjoblog.sas Normal file
View File

@@ -0,0 +1,268 @@
/**
@file
@brief Extract the log from a completed SAS Viya Job
@details Extracts log from a Viya job and writes it out to a fileref
To query the job, you need the URI. Sample code for achieving this
is provided below.
## Example
First, compile the macros:
filename mc url
"https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
Next, create a job (in this case, a web service):
filename ft15f001 temp;
parmcards4;
data ;
rand=ranuni(0)*1000;
do x=1 to rand;
y=rand*4;
output;
end;
run;
proc sort data=&syslast
by descending y;
run;
;;;;
%mv_createwebservice(path=/Public/temp,name=demo)
Execute it:
%mv_jobexecute(path=/Public/temp
,name=demo
,outds=work.info
)
Wait for it to finish, and grab the uri:
data _null_;
set work.info;
if method='GET' and rel='self';
call symputx('uri',uri);
run;
Finally, fetch the log:
%mv_getjoblog(uri=&uri,outref=mylog)
This macro is used by the mv_jobwaitfor.sas macro, which is generally a more
convenient way to wait for the job to finish before fetching the log.
@param [in] access_token_var= The global macro variable to contain the access token
@param [in] mdebug= set to 1 to enable DEBUG messages
@param [in] grant_type= valid values:
@li password
@li authorization_code
@li detect - will check if access_token exists, if not will use sas_services if
a SASStudioV session else authorization_code. Default option.
@li sas_services - will use oauth_bearer=sas_services.
@param [in] uri= The uri of the running job for which to fetch the status,
in the format `/jobExecution/jobs/$UUID/state` (unquoted).
@param [out] outref= The output fileref to which to APPEND the log (is always
appended).
@version VIYA V.03.04
@author Allan Bowe, source: https://github.com/sasjs/core
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mf_getplatform.sas
@li mf_existfileref.sas
@li ml_json.sas
**/
%macro mv_getjoblog(uri=0,outref=0
,contextName=SAS Job Execution compute context
,access_token_var=ACCESS_TOKEN
,grant_type=sas_services
,mdebug=0
);
%local oauth_bearer;
%if &grant_type=detect %then %do;
%if %symexist(&access_token_var) %then %let grant_type=authorization_code;
%else %let grant_type=sas_services;
%end;
%if &grant_type=sas_services %then %do;
%let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=;
%end;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services
)
,mac=&sysmacroname
,msg=%str(Invalid value for grant_type: &grant_type)
)
/* validation in datastep for better character safety */
%local errmsg errflg;
data _null_;
uri=symget('uri');
if length(uri)<12 then do;
call symputx('errflg',1);
call symputx('errmsg',"URI is invalid (too short) - '&uri'",'l');
end;
if scan(uri,-1)='state' or scan(uri,1) ne 'jobExecution' then do;
call symputx('errflg',1);
call symputx('errmsg',
"URI should be in format /jobExecution/jobs/$$$$UUID$$$$"
!!" but is actually like: &uri",'l');
end;
run;
%mp_abort(iftrue=(&errflg=1)
,mac=&sysmacroname
,msg=%str(&errmsg)
)
%mp_abort(iftrue=(&outref=0)
,mac=&sysmacroname
,msg=%str(Output fileref should be provided)
)
%if %mf_existfileref(&outref) ne 1 %then %do;
filename &outref temp;
%end;
options noquotelenmax;
%local base_uri; /* location of rest apis */
%let base_uri=%mf_getplatform(VIYARESTAPI);
/* prepare request*/
%local fname1;
%let fname1=%mf_getuniquefileref();
proc http method='GET' out=&fname1 &oauth_bearer
url="&base_uri&uri";
headers
%if &grant_type=authorization_code %then %do;
"Authorization"="Bearer &&&access_token_var"
%end;
;
run;
%if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then
%do;
data _null_;infile &fname1;input;putlog _infile_;run;
%mp_abort(mac=&sysmacroname
,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
)
%end;
%local fname2 fname3 fpath1 fpath2 fpath3;
%let fname2=%mf_getuniquefileref();
%let fname3=%mf_getuniquefileref();
%let fpath1=%sysfunc(pathname(&fname1));
%let fpath2=%sysfunc(pathname(&fname2));
%let fpath3=%sysfunc(pathname(&fname3));
/* compile the lua JSON module */
%ml_json()
/* read using LUA - this allows the code to be of any length */
data _null_;
file "&fpath3..lua";
put '
infile = io.open (sas.symget("fpath1"), "r")
outfile = io.open (sas.symget("fpath2"), "w")
io.input(infile)
local resp=json.decode(io.read())
local logloc=resp["logLocation"]
outfile:write(logloc)
io.close(infile)
io.close(outfile)
';
run;
%inc "&fpath3..lua";
/* get log path*/
%let errflg=1;
%let errmsg=No entry in &fname2 fileref;
data _null_;
infile &fname2;
input;
uri=_infile_;
if length(uri)<12 then do;
call symputx('errflg',1);
call symputx('errmsg',"URI is invalid (too short) - '&uri'",'l');
end;
if scan(uri,1) ne 'files' or scan(uri,2) ne 'files' then do;
call symputx('errflg',1);
call symputx('errmsg',
"URI should be in format /files/files/$$$$UUID$$$$"
!!" but is actually like: &uri",'l');
end;
call symputx('errflg',0,'l');
call symputx('logloc',uri,'l');
run;
%mp_abort(iftrue=(&errflg=1)
,mac=&sysmacroname
,msg=%str(&errmsg)
)
/* we have a log uri - now fetch the log */
proc http method='GET' out=&fname1 &oauth_bearer
url="&base_uri&logloc/content";
headers
%if &grant_type=authorization_code %then %do;
"Authorization"="Bearer &&&access_token_var"
%end;
;
run;
%if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then
%do;
data _null_;infile &fname1;input;putlog _infile_;run;
%mp_abort(mac=&sysmacroname
,msg=%str(logfetch: &SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
)
%end;
data _null_;
file "&fpath3..lua";
put '
infile = io.open (sas.symget("fpath1"), "r")
outfile = io.open (sas.symget("fpath2"), "w")
io.input(infile)
local resp=json.decode(io.read())
for i, v in pairs(resp["items"]) do
outfile:write(v.line,"\n")
end
io.close(infile)
io.close(outfile)
';
run;
%inc "&fpath3..lua";
/* write log out to the specified fileref */
data _null_;
infile &fname2 end=last;
file &outref mod;
if _n_=1 then do;
put "/** SASJS Viya Job Log Extract start: &uri **/";
end;
input;
put _infile_;
%if &mdebug=1 %then %do;
putlog _infile_;
%end;
if last then do;
put "/** SASJS Viya Job Log Extract end: &uri **/";
end;
run;
%if &mdebug=0 %then %do;
filename &fname1 clear;
filename &fname2 clear;
filename &fname3 clear;
%end;
%else %do;
%put _local_;
%end;
%mend;

View File

@@ -101,7 +101,7 @@
%let oauth_bearer=oauth_bearer=sas_services; %let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=; %let &access_token_var=;
%end; %end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services and &grant_type ne sas_services
) )

View File

@@ -72,7 +72,7 @@
%let oauth_bearer=oauth_bearer=sas_services; %let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=; %let &access_token_var=;
%end; %end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services and &grant_type ne sas_services
) )

352
viya/mv_jobflow.sas Normal file
View File

@@ -0,0 +1,352 @@
/**
@file
@brief Execute a series of job flows
@details Very (very) simple flow manager. Jobs execute in sequential waves,
all previous waves must finish successfully.
The input table is formed as per below. Each observation represents one job.
Each variable is converted into a macro variable with the same name.
## Input table (minimum variables needed)
@li _PROGRAM - Provides the path to the job itself
@li FLOW_ID - Numeric value, provides sequential ordering capability. Is
optional, will default to 0 if not provided.
@li _CONTEXTNAME - Dictates which context should be used to run the job. If
blank, or not provided, will default to `SAS Job Execution compute context`.
Any additional variables provided in this table are converted into macro
variables and passed into the relevant job.
|_PROGRAM| FLOW_ID (optional)| _CONTEXTNAME (optional) |
|---|---|---|
|/Public/jobs/somejob1|0|SAS Job Execution compute context|
|/Public/jobs/somejob2|0|SAS Job Execution compute context|
## Output table (minimum variables produced)
@li _PROGRAM - the SAS Drive path of the job
@li URI - the URI of the executed job
@li STATE - the completed state of the job
@li TIMESTAMP - the datetime that the job completed
@li JOBPARAMS - the parameters that were passed to the job
@li FLOW_ID - the id of the flow in which the job was executed
![https://i.imgur.com/nZE9PvT.png](https://i.imgur.com/nZE9PvT.png)
To avoid hammering the box with many hits in rapid succession, a one
second pause is made between every request.
## Example
First, compile the macros:
filename mc url
"https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
Next, create some jobs (in this case, as web services):
filename ft15f001 temp;
parmcards4;
%put this is job: &_program;
%put this was run in flow &flow_id;
data ;
rand=ranuni(0)*&macrovar1;
do x=1 to rand;
y=rand*&macrovar2;
if y=100 then abort;
output;
end;
run;
;;;;
%mv_createwebservice(path=/Public/temp,name=demo1)
%mv_createwebservice(path=/Public/temp,name=demo2)
Prepare an input table with 60 executions:
data work.inputjobs;
_contextName='SAS Job Execution compute context';
do flow_id=1 to 3;
do i=1 to 20;
_program='/Public/temp/demo1';
macrovar1=10*i;
macrovar2=4*i;
output;
i+1;
_program='/Public/temp/demo2';
macrovar1=40*i;
macrovar2=44*i;
output;
end;
end;
run;
Trigger the flow
%mv_jobflow(inds=work.inputjobs
,maxconcurrency=4
,outds=work.results
,outref=myjoblog
)
data _null_;
infile myjoblog;
input; put _infile_;
run;
@param [in] access_token_var= The global macro variable to contain the access
token
@param [in] grant_type= valid values:
@li password
@li authorization_code
@li detect - will check if access_token exists, if not will use
sas_services if a SASStudioV session else authorization_code. Default
option.
@li sas_services - will use oauth_bearer=sas_services
@param [in] inds= The input dataset containing a list of jobs and parameters
@param [in] maxconcurrency= The max number of parallel jobs to run. Default=8.
@param [in] raise_err=0 Set to 1 to raise SYSCC when a job does not complete
succcessfully
@param [in] mdebug= set to 1 to enable DEBUG messages
@param [out] outds= The output dataset containing the results
@param [out] outref= The output fileref to which to append the log file(s).
@version VIYA V.03.05
@author Allan Bowe, source: https://github.com/sasjs/core
<h4> SAS Macros </h4>
@li mf_nobs.sas
@li mp_abort.sas
@li mf_getplatform.sas
@li mf_getuniquefileref.sas
@li mf_existvarlist.sas
@li mv_jobwaitfor.sas
@li mv_jobexecute.sas
**/
%macro mv_jobflow(inds=0,outds=work.mv_jobflow
,maxconcurrency=8
,access_token_var=ACCESS_TOKEN
,grant_type=sas_services
,outref=0
,raise_err=0
,mdebug=0
);
%local oauth_bearer;
%if &grant_type=detect %then %do;
%if %symexist(&access_token_var) %then %let grant_type=authorization_code;
%else %let grant_type=sas_services;
%end;
%if &grant_type=sas_services %then %do;
%let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=;
%end;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services
)
,mac=&sysmacroname
,msg=%str(Invalid value for grant_type: &grant_type)
)
%mp_abort(iftrue=("&inds"="0")
,mac=&sysmacroname
,msg=%str(Input dataset was not provided)
)
%mp_abort(iftrue=(%mf_existVarList(&inds,_PROGRAM)=0)
,mac=&sysmacroname
,msg=%str(The _PROGRAM column must exist on input dataset &inds)
)
%mp_abort(iftrue=(&maxconcurrency<1)
,mac=&sysmacroname
,msg=%str(The maxconcurrency variable should be a positive integer)
)
/* set defaults if not provided */
%if %mf_existVarList(&inds,_CONTEXTNAME FLOW_ID)=0 %then %do;
data &inds;
%if %mf_existvarList(&inds,_CONTEXTNAME)=0 %then %do;
length _CONTEXTNAME $128;
retain _CONTEXTNAME "SAS Job Execution compute context";
%end;
%if %mf_existvarList(&inds,FLOW_ID)=0 %then %do;
retain FLOW_ID 0;
%end;
set &inds;
run;
%end;
%local missings;
proc sql noprint;
select count(*) into: missings
from &inds
where flow_id is null or _program is null;
%mp_abort(iftrue=(&missings>0)
,mac=&sysmacroname
,msg=%str(input dataset has &missings missing values for FLOW_ID or _PROGRAM)
)
%if %mf_nobs(&inds)=0 %then %do;
%put No observations in &inds! Leaving macro &sysmacroname;
%return;
%end;
/* ensure output table is available */
data &outds;run;
proc sql;
drop table &outds;
options noquotelenmax;
%local base_uri; /* location of rest apis */
%let base_uri=%mf_getplatform(VIYARESTAPI);
/* get flows */
proc sort data=&inds;
by flow_id;
run;
data _null_;
set &inds (keep=flow_id) end=last;
by flow_id;
if last.flow_id then do;
cnt+1;
call symputx(cats('flow',cnt),flow_id,'l');
end;
if last then call symputx('flowcnt',cnt,'l');
run;
/* prepare temporary datasets and frefs */
%local fid jid jds jjson jdsapp jdsrunning jdswaitfor jfref;
data;run;%let jds=&syslast;
data;run;%let jjson=&syslast;
data;run;%let jdsapp=&syslast;
data;run;%let jdsrunning=&syslast;
data;run;%let jdswaitfor=&syslast;
%let jfref=%mf_getuniquefileref();
/* start loop */
%do fid=1 %to &flowcnt;
%put preparing job attributes for flow &&flow&fid;
%local jds jcnt;
data &jds(drop=_contextName _program);
set &inds(where=(flow_id=&&flow&fid));
if _contextName='' then _contextName="SAS Job Execution compute context";
call symputx(cats('job',_n_),_program,'l');
call symputx(cats('context',_n_),_contextName,'l');
call symputx('jcnt',_n_,'l');
run;
%put exporting job variables in json format;
%do jid=1 %to &jcnt;
data &jjson;
set &jds;
if _n_=&jid then do;
output;
stop;
end;
run;
proc json out=&jfref;
export &jjson / nosastags fmtnumeric;
run;
data _null_;
infile &jfref lrecl=32767;
input;
jparams='jparams'!!left(symget('jid'));
call symputx(jparams,substr(_infile_,3,length(_infile_)-4));
run;
%local jobuid&jid;
%let jobuid&jid=0; /* used in next loop */
%end;
%local concurrency completed;
%let concurrency=0;
%let completed=0;
proc sql; drop table &jdsrunning;
%do jid=1 %to &jcnt;
/**
* now we can execute the jobs up to the maxconcurrency setting
*/
%if "&&job&jid" ne "0" %then %do; /* this var is zero if job finished */
/* check to see if the job finished in the previous round */
%if %sysfunc(exist(&outds))=1 %then %do;
%local jobcheck; %let jobcheck=0;
proc sql noprint;
select count(*) into: jobcheck
from &outds where uuid="&&jobuid&jid";
%if &jobcheck>0 %then %do;
%put &&job&jid in flow &fid with uid &&jobuid&jid completed!;
%let job&jid=0;
%end;
%end;
/* check if job was triggered and if so, if we have enough slots to run */
%if "&&jobuid&jid"="0" and &concurrency<&maxconcurrency %then %do;
%local jobname jobpath;
%let jobname=%scan(&&job&jid,-1,/);
%let jobpath=
%substr(&&job&jid,1,%length(&&job&jid)-%length(&jobname)-1);
%put executing &jobpath/&jobname with paramstring &&jparams&jid;
%mv_jobexecute(path=&jobpath
,name=&jobname
,paramstring=%superq(jparams&jid)
,outds=&jdsapp
)
data &jdsapp;
format jobparams $32767.;
set &jdsapp(where=(method='GET' and rel='state'));
jobparams=symget("jparams&jid");
/* uri here has the /state suffix */
uuid=scan(uri,-2,'/');
call symputx("jobuid&jid",uuid,'l');
run;
proc append base=&jdsrunning data=&jdsapp;
run;
%let concurrency=%eval(&concurrency+1);
/* sleep one second after every request to smooth the impact */
data _null_;
call sleep(1,1);
run;
%end;
%end;
%if &jid=&jcnt %then %do;
/* we are at the end of the loop - time to see which jobs have finished */
%mv_jobwaitfor(ANY,inds=&jdsrunning,outds=&jdswaitfor,outref=&outref
,raise_err=&raise_err)
%local done;
%let done=%mf_nobs(&jdswaitfor);
%if &done>0 %then %do;
%let completed=%eval(&completed+&done);
%let concurrency=%eval(&concurrency-&done);
data &jdsapp;
set &jdswaitfor;
flow_id=&&flow&fid;
uuid=scan(uri,-1,'/');
run;
proc append base=&outds data=&jdsapp;
run;
%end;
proc sql;
delete from &jdsrunning
where uuid in (select uuid from &outds
where state in ('canceled','completed','failed')
);
/* loop again if jobs are left */
%if &completed < &jcnt %then %do;
%let jid=0;
%put looping flow &fid again - &completed of &jcnt jobs completed,
&concurrency jobs running;
%end;
%end;
%end;
/* back up and execute the next flow */
%end;
%if &mdebug=1 %then %do;
%put _local_;
%end;
%mend;

View File

@@ -70,9 +70,11 @@
following format: `/jobExecution/jobs/&JOBID./state` and the corresponding following format: `/jobExecution/jobs/&JOBID./state` and the corresponding
job name. The uri should be in a `uri` variable, and the job path/name job name. The uri should be in a `uri` variable, and the job path/name
should be in a `_program` variable. should be in a `_program` variable.
@param [in] raise_err=0 Set to 1 to raise SYSCC when a job does not complete
succcessfully
@param [out] outds= The output dataset containing the list of states by job @param [out] outds= The output dataset containing the list of states by job
(default=work.mv_jobexecute) (default=work.mv_jobexecute)
@param [out] outref= A fileref to which the spawned job logs should be appended.
@version VIYA V.03.04 @version VIYA V.03.04
@author Allan Bowe, source: https://github.com/sasjs/core @author Allan Bowe, source: https://github.com/sasjs/core
@@ -81,8 +83,10 @@
@li mp_abort.sas @li mp_abort.sas
@li mf_getplatform.sas @li mf_getplatform.sas
@li mf_getuniquefileref.sas @li mf_getuniquefileref.sas
@li mf_getuniquelibref.sas
@li mf_existvar.sas @li mf_existvar.sas
@li mf_nobs.sas @li mf_nobs.sas
@li mv_getjoblog.sas
**/ **/
@@ -91,6 +95,8 @@
,grant_type=sas_services ,grant_type=sas_services
,inds=0 ,inds=0
,outds=work.mv_jobwaitfor ,outds=work.mv_jobwaitfor
,outref=0
,raise_err=0
); );
%local oauth_bearer; %local oauth_bearer;
%if &grant_type=detect %then %do; %if &grant_type=detect %then %do;
@@ -101,7 +107,7 @@
%let oauth_bearer=oauth_bearer=sas_services; %let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=; %let &access_token_var=;
%end; %end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services and &grant_type ne sas_services
) )
@@ -134,7 +140,7 @@ options noquotelenmax;
data _null_; data _null_;
length jobparams $32767; length jobparams $32767;
set &inds end=last; set &inds end=last;
call symputx(cats('joburi',_n_),uri,'l'); call symputx(cats('joburi',_n_),substr(uri,1,55),'l');
call symputx(cats('jobname',_n_),_program,'l'); call symputx(cats('jobname',_n_),_program,'l');
call symputx(cats('jobparams',_n_),jobparams,'l'); call symputx(cats('jobparams',_n_),jobparams,'l');
if last then call symputx('uricnt',_n_,'l'); if last then call symputx('uricnt',_n_,'l');
@@ -149,7 +155,7 @@ run;
%let fname0=%mf_getuniquefileref(); %let fname0=%mf_getuniquefileref();
data &outds; data &outds;
format _program uri $128. state $32. timestamp datetime19. jobparams $32767.; format _program uri $128. state $32. stateDetails $32. timestamp datetime19. jobparams $32767.;
stop; stop;
run; run;
@@ -157,7 +163,7 @@ run;
%do i=1 %to &uricnt; %do i=1 %to &uricnt;
%if "&&joburi&i" ne "0" %then %do; %if "&&joburi&i" ne "0" %then %do;
proc http method='GET' out=&fname0 &oauth_bearer url="&base_uri/&&joburi&i"; proc http method='GET' out=&fname0 &oauth_bearer url="&base_uri/&&joburi&i";
headers "Accept"="text/plain" headers "Accept"="application/json"
%if &grant_type=authorization_code %then %do; %if &grant_type=authorization_code %then %do;
"Authorization"="Bearer &&&access_token_var" "Authorization"="Bearer &&&access_token_var"
%end; ; %end; ;
@@ -171,21 +177,36 @@ run;
%end; %end;
%let status=notset; %let status=notset;
%local libref1;
%let libref1=%mf_getuniquelibref();
libname &libref1 json fileref=&fname0;
data _null_; data _null_;
infile &fname0; length state stateDetails $32;
input; set &libref1..root;
call symputx('status',_infile_,'l'); call symputx('status',state,'l');
call symputx('stateDetails',stateDetails,'l');
run; run;
libname &libref1 clear;
%if &status=completed or &status=failed or &status=canceled %then %do; %if &status=completed or &status=failed or &status=canceled %then %do;
%local plainuri;
%let plainuri=%substr(&&joburi&i,1,55);
proc sql; proc sql;
insert into &outds set insert into &outds set
_program="&&jobname&i", _program="&&jobname&i",
uri="&&joburi&i", uri="&plainuri",
state="&status", state="&status",
stateDetails=symget("stateDetails"),
timestamp=datetime(), timestamp=datetime(),
jobparams=symget("jobparams&i"); jobparams=symget("jobparams&i");
%let joburi&i=0; /* do not re-check */ %let joburi&i=0; /* do not re-check */
/* fetch log */
%if %str(&outref) ne 0 %then %do;
%mv_getjoblog(uri=&plainuri,outref=&outref)
%end;
%end; %end;
%else %if &status=idle or &status=pending or &status=running %then %do; %else %if &status=idle or &status=pending or &status=running %then %do;
data _null_; data _null_;
@@ -197,6 +218,16 @@ run;
,msg=%str(status &status not expected!!) ,msg=%str(status &status not expected!!)
) )
%end; %end;
%if (&raise_err) %then %do;
%if (&status = canceled or &status = failed or %length(&stateDetails)>0) %then %do;
%if ("&stateDetails" = "%str(war)ning") %then %let SYSCC=4;
%else %let SYSCC=5;
%put %str(ERR)OR: Job &&jobname&i. did not complete successfully. &stateDetails;
%return;
%end;
%end;
%end; %end;
%if &i=&uricnt %then %do; %if &i=&uricnt %then %do;
%local goback; %local goback;

View File

@@ -5,14 +5,16 @@
This macro will obtain the Consul Token and use that to call the Web Service. This macro will obtain the Consul Token and use that to call the Web Service.
more info: https://developer.sas.com/reference/auth/#register more info: https://developer.sas.com/reference/auth/#register
and: http://proc-x.com/2019/01/authentication-to-sas-viya-a-couple-of-approaches/ and:
http://proc-x.com/2019/01/authentication-to-sas-viya-a-couple-of-approaches
The default viyaroot location is /opt/sas/viya/config The default viyaroot location is /opt/sas/viya/config
Usage: Usage:
%* compile macros; %* compile macros;
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;
%* specific client with just openid scope; %* specific client with just openid scope;
@@ -33,7 +35,8 @@
@param client_id= The client name. Auto generated if blank. @param client_id= The client name. Auto generated if blank.
@param client_secret= Client secret Auto generated if client is blank. @param client_secret= Client secret Auto generated if client is blank.
@param scopes= list of space-seperated unquoted scopes (default is openid) @param scopes= list of space-seperated unquoted scopes (default is openid)
@param grant_type= valid values are "password" or "authorization_code" (unquoted) @param grant_type= valid values are "password" or "authorization_code"
(unquoted)
@param outds= the dataset to contain the registered client id and secret @param outds= the dataset to contain the registered client id and secret
@param access_token_validity= The duration of validity of the access token @param access_token_validity= The duration of validity of the access token
in seconds. A value of DEFAULT will omit the entry (and use system default) in seconds. A value of DEFAULT will omit the entry (and use system default)
@@ -78,15 +81,16 @@
,refresh_token_validity=DEFAULT ,refresh_token_validity=DEFAULT
,outjson=_null_ ,outjson=_null_
); );
%local consul_token fname1 fname2 fname3 libref access_token url; %local consul_token fname1 fname2 fname3 libref access_token url tokloc;
%if client_name=DEFAULT %then %let client_name= %if client_name=DEFAULT %then %let client_name=
Generated by %mf_getuser() on %sysfunc(datetime(),datetime19.) using SASjs; Generated by %mf_getuser() on %sysfunc(datetime(),datetime19.) using SASjs;
options noquotelenmax; options noquotelenmax;
/* first, get consul token needed to get client id / secret */ /* first, get consul token needed to get client id / secret */
%let tokloc=/etc/SASSecurityCertificateFramework/tokens/consul/default;
data _null_; data _null_;
infile "%mf_loc(VIYACONFIG)/etc/SASSecurityCertificateFramework/tokens/consul/default/client.token"; infile "%mf_loc(VIYACONFIG)&tokloc/client.token";
input token:$64.; input token:$64.;
call symputx('consul_token',token); call symputx('consul_token',token);
run; run;
@@ -97,7 +101,8 @@ run;
/* request the client details */ /* request the client details */
%let fname1=%mf_getuniquefileref(); %let fname1=%mf_getuniquefileref();
proc http method='POST' out=&fname1 proc http method='POST' out=&fname1
url="&base_uri/SASLogon/oauth/clients/consul?callback=false%str(&)serviceId=app"; url="&base_uri/SASLogon/oauth/clients/consul?callback=false%str(&)%trim(
)serviceId=app";
headers "X-Consul-Token"="&consul_token"; headers "X-Consul-Token"="&consul_token";
run; run;
@@ -122,7 +127,8 @@ run;
%let scopes=%sysfunc(coalescec(&scopes,openid)); %let scopes=%sysfunc(coalescec(&scopes,openid));
%let scopes=%mf_getquotedstr(&scopes,QUOTE=D,indlm=|); %let scopes=%mf_getquotedstr(&scopes,QUOTE=D,indlm=|);
%let grant_type=%mf_getquotedstr(&grant_type,QUOTE=D,indlm=|); %let grant_type=%mf_getquotedstr(&grant_type,QUOTE=D,indlm=|);
%let required_user_groups=%mf_getquotedstr(&required_user_groups,QUOTE=D,indlm=|); %let required_user_groups=
%mf_getquotedstr(&required_user_groups,QUOTE=D,indlm=|);
data _null_; data _null_;
file &fname2; file &fname2;
@@ -139,9 +145,11 @@ data _null_;
if reqd_groups = '""' then reqd_groups =''; if reqd_groups = '""' then reqd_groups ='';
else reqd_groups=cats(',"required_user_groups":[',reqd_groups,']'); else reqd_groups=cats(',"required_user_groups":[',reqd_groups,']');
autoapprove=trim(symget('autoapprove')); autoapprove=trim(symget('autoapprove'));
if not missing(autoapprove) then autoapprove=cats(',"autoapprove":',autoapprove); if not missing(autoapprove) then autoapprove=
cats(',"autoapprove":',autoapprove);
use_session=trim(symget('use_session')); use_session=trim(symget('use_session'));
if not missing(use_session) then use_session=cats(',"use_session":',use_session); if not missing(use_session) then use_session=
cats(',"use_session":',use_session);
put '{' clientid ; put '{' clientid ;
put clientsecret ; put clientsecret ;
@@ -206,10 +214,12 @@ run;
%put GRANT_TYPE=&grant_type; %put GRANT_TYPE=&grant_type;
%put; %put;
%if %index(%superq(grant_type),authorization_code) %then %do; %if %index(%superq(grant_type),authorization_code) %then %do;
/* cannot use base_uri here as it includes the protocol which may be incorrect externally */ /* cannot use base_uri here as it includes the protocol which may be incorrect
%put NOTE: The developer must also register below and select 'openid' to get the grant code:; externally */
%put NOTE: Visit the link below and select 'openid' to get the grant code:;
%put NOTE- ; %put NOTE- ;
%put NOTE- &url/SASLogon/oauth/authorize?client_id=&client_id%str(&)response_type=code; %put NOTE- &url/SASLogon/oauth/authorize?client_id=&client_id%str(&)%trim(
)response_type=code;
%put NOTE- ; %put NOTE- ;
%end; %end;

View File

@@ -15,7 +15,8 @@
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;
@@ -31,13 +32,15 @@
@param outds= A dataset containing access_token and refresh_token @param outds= A dataset containing access_token and refresh_token
@param client_id= The client name @param client_id= The client name
@param client_secret= client secret @param client_secret= client secret
@param grant_type= valid values are "password" or "authorization_code" (unquoted). @param grant_type= valid values are "password" or "authorization_code"
The default is authorization_code. (unquoted). The default is authorization_code.
@param code= If grant_type=authorization_code then provide the necessary code here @param code= If grant_type=authorization_code then provide the necessary code
here
@param user= If grant_type=password then provide the username here @param user= If grant_type=password then provide the username here
@param pass= If grant_type=password then provide the password here @param pass= If grant_type=password then provide the password here
@param access_token_var= The global macro variable to contain the access token @param access_token_var= The global macro variable to contain the access token
@param refresh_token_var= The global macro variable to contain the refresh token @param refresh_token_var= The global macro variable to contain the refresh
token
@param base_uri= The Viya API server location @param base_uri= The Viya API server location
@version VIYA V.03.04 @version VIYA V.03.04
@@ -88,7 +91,8 @@
,msg=%str(Authorization code required) ,msg=%str(Authorization code required)
) )
%mp_abort(iftrue=(&grant_type=password and (%str(&user)=%str() or %str(&pass)=%str())) %mp_abort(iftrue=(
&grant_type=password and (%str(&user)=%str() or %str(&pass)=%str()))
,mac=&sysmacroname ,mac=&sysmacroname
,msg=%str(username / password required) ,msg=%str(username / password required)
) )

View File

@@ -32,12 +32,13 @@
@param outds= A dataset containing access_token and refresh_token @param outds= A dataset containing access_token and refresh_token
@param client_id= The client name (alternative to inds) @param client_id= The client name (alternative to inds)
@param client_secret= client secret (alternative to inds) @param client_secret= client secret (alternative to inds)
@param grant_type= valid values are "password" or "authorization_code" (unquoted). @param grant_type= valid values are "password" or "authorization_code"
The default is authorization_code. (unquoted). The default is authorization_code.
@param user= If grant_type=password then provide the username here @param user= If grant_type=password then provide the username here
@param pass= If grant_type=password then provide the password here @param pass= If grant_type=password then provide the password here
@param access_token_var= The global macro variable to contain the access token @param access_token_var= The global macro variable to contain the access token
@param refresh_token_var= The global macro variable containing the refresh token @param refresh_token_var= The global macro variable containing the refresh
token
@version VIYA V.03.04 @version VIYA V.03.04
@author Allan Bowe, source: https://github.com/sasjs/core @author Allan Bowe, source: https://github.com/sasjs/core
@@ -72,7 +73,8 @@ options noquotelenmax;
,msg=%str(Invalid value for grant_type: &grant_type) ,msg=%str(Invalid value for grant_type: &grant_type)
) )
%mp_abort(iftrue=(&grant_type=password and (%str(&user)=%str() or %str(&pass)=%str())) %mp_abort(
iftrue=(&grant_type=password and (%str(&user)=%str() or %str(&pass)=%str()))
,mac=&sysmacroname ,mac=&sysmacroname
,msg=%str(username / password required) ,msg=%str(username / password required)
) )

Some files were not shown because too many files have changed in this diff Show More