From 0135dd6c8f692e9b38653a3ae67c0017abc02cd7 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Wed, 17 Mar 2021 07:40:48 +0000 Subject: [PATCH 01/70] chore: updating gitpod files --- .gitpod.dockerfile | 10 ++++++++++ .gitpod.yml | 8 +++++++- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 .gitpod.dockerfile diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile new file mode 100644 index 0000000..1a4cf66 --- /dev/null +++ b/.gitpod.dockerfile @@ -0,0 +1,10 @@ + +FROM gitpod/workspace-full + +RUN sudo apt-get update \ + && sudo apt-get install -y \ + doxygen \ + && npm i -g npm@latest \ + && npm i -g @sasjs/cli \ + && npm i \ + && sudo rm -rf /var/lib/apt/lists/* \ No newline at end of file diff --git a/.gitpod.yml b/.gitpod.yml index ce78b58..3e3731a 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,3 +1,9 @@ + +tasks: + - init: npm i && clear + +image: + file: .gitpod.dockerfile vscode: extensions: - - sasjs.sasjs-for-vscode@1.2.6:AJmar85B1uSEapxRaRQGrQ== \ No newline at end of file + - sasjs.sasjs-for-vscode \ No newline at end of file From 521d128afe35db83beeabb3edd5eeea1291939f2 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Wed, 17 Mar 2021 09:21:16 +0000 Subject: [PATCH 02/70] chore: gitpod file --- .gitpod.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitpod.yml b/.gitpod.yml index 3e3731a..de29988 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,4 +1,3 @@ - tasks: - init: npm i && clear @@ -6,4 +5,4 @@ image: file: .gitpod.dockerfile vscode: extensions: - - sasjs.sasjs-for-vscode \ No newline at end of file + - sasjs.sasjs-for-vscode@1.4.3:LY5VLf6H5R3u7nqVRnCQRw== \ No newline at end of file From 7db207dd1cbc7be64aa86825898e640352bf1e64 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Fri, 19 Mar 2021 20:29:35 +0100 Subject: [PATCH 03/70] chore: updating all.sas --- all.sas | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/all.sas b/all.sas index d128399..7194df4 100644 --- a/all.sas +++ b/all.sas @@ -13910,6 +13910,8 @@ libname &libref; @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). @@ -13933,6 +13935,7 @@ libname &libref; ,access_token_var=ACCESS_TOKEN ,grant_type=sas_services ,outref=0 + ,raise_err=0 ,mdebug=0 ); %local oauth_bearer; @@ -14111,7 +14114,8 @@ data;run;%let jdswaitfor=&syslast; %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) + %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; @@ -14218,6 +14222,8 @@ data;run;%let jdswaitfor=&syslast; following format: `/jobExecution/jobs/&JOBID./state` and the corresponding job name. The uri should be in a `uri` variable, and the job path/name 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 (default=work.mv_jobexecute) @param [out] outref= A fileref to which the spawned job logs should be appended. @@ -14229,6 +14235,7 @@ data;run;%let jdswaitfor=&syslast; @li mp_abort.sas @li mf_getplatform.sas @li mf_getuniquefileref.sas + @li mf_getuniquelibref.sas @li mf_existvar.sas @li mf_nobs.sas @li mv_getjoblog.sas @@ -14241,6 +14248,7 @@ data;run;%let jdswaitfor=&syslast; ,inds=0 ,outds=work.mv_jobwaitfor ,outref=0 + ,raise_err=0 ); %local oauth_bearer; %if &grant_type=detect %then %do; @@ -14284,7 +14292,7 @@ options noquotelenmax; data _null_; length jobparams $32767; set &inds end=last; - call symputx(cats('joburi',_n_),substr(uri,1,55)!!'/state','l'); + call symputx(cats('joburi',_n_),substr(uri,1,55),'l'); call symputx(cats('jobname',_n_),_program,'l'); call symputx(cats('jobparams',_n_),jobparams,'l'); if last then call symputx('uricnt',_n_,'l'); @@ -14299,7 +14307,7 @@ run; %let fname0=%mf_getuniquefileref(); 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; run; @@ -14307,7 +14315,7 @@ run; %do i=1 %to &uricnt; %if "&&joburi&i" ne "0" %then %do; 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; "Authorization"="Bearer &&&access_token_var" %end; ; @@ -14321,12 +14329,20 @@ run; %end; %let status=notset; + + %local libref1; + %let libref1=%mf_getuniquelibref(); + libname &libref1 json fileref=&fname0; + data _null_; - infile &fname0; - input; - call symputx('status',_infile_,'l'); + length state stateDetails $32; + set &libref1..root; + call symputx('status',state,'l'); + call symputx('stateDetails',stateDetails,'l'); run; + libname &libref1 clear; + %if &status=completed or &status=failed or &status=canceled %then %do; %local plainuri; %let plainuri=%substr(&&joburi&i,1,55); @@ -14335,6 +14351,7 @@ run; _program="&&jobname&i", uri="&plainuri", state="&status", + stateDetails=symget("stateDetails"), timestamp=datetime(), jobparams=symget("jobparams&i"); %let joburi&i=0; /* do not re-check */ @@ -14353,6 +14370,16 @@ run; ,msg=%str(status &status not expected!!) ) %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; %if &i=&uricnt %then %do; %local goback; From 4b375e0b8ca63d123282b2e37a6138d515e8c771 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Fri, 19 Mar 2021 20:34:16 +0100 Subject: [PATCH 04/70] fix: leading zero in time component of mf_uid() --- all.sas | 6 +++--- base/mf_uid.sas | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/all.sas b/all.sas index 7194df4..629b233 100644 --- a/all.sas +++ b/all.sas @@ -1426,12 +1426,12 @@ Usage: %mend;/** @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__<3randomDigits> %put %mf_uid(); - @version 9.2 + @version 9.3 @author Allan Bowe **/ @@ -1440,7 +1440,7 @@ Usage: )/*/STORE SOURCE*/; %local today now; %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) diff --git a/base/mf_uid.sas b/base/mf_uid.sas index 53391f4..01eee7f 100644 --- a/base/mf_uid.sas +++ b/base/mf_uid.sas @@ -1,11 +1,11 @@ /** @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__<3randomDigits> %put %mf_uid(); - @version 9.2 + @version 9.3 @author Allan Bowe **/ @@ -14,7 +14,7 @@ )/*/STORE SOURCE*/; %local today now; %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) From f356e1f3515564ef57e36f4a8441b325b20b9588 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sun, 21 Mar 2021 22:23:50 +0100 Subject: [PATCH 05/70] chore: updating CONTRIBUTING, preventing files with spaces being added, adding git hooks package --- .gitignore | 3 +++ CONTRIBUTING.md | 3 +-- package-lock.json | 24 +++++++++++++++++++++++- package.json | 6 +++++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index ffc0011..a6210ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ node_modules .DS_Store sasjsbuild/ + +# avoid filenames with spaces being committed to source control +**\ ** \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 23da787..ec24915 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,6 +27,5 @@ To contribute: 1. Create your feature branch (`git checkout -b myfeature`) 2. Make your change 3. Update the `all.sas` file (`python3 build.py`) -4. Commit the change, using the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0) standard -5. Push and make a PR +4. Push and make a PR diff --git a/package-lock.json b/package-lock.json index 480c4d2..27ec6fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,29 @@ "": { "name": "@sasjs/core", "version": "1.0.0", - "license": "MIT" + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-git-hooks": "^1.0.5" + } + }, + "node_modules/node-git-hooks": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-git-hooks/-/node-git-hooks-1.0.5.tgz", + "integrity": "sha512-05rULsJy8z2OvXTQFZv9fN20uo186EYgJYQjkL1OjnXj53QivOAGUzZilZ/MX8OmPRaN+deJBtlvMRydpdfnqA==", + "bin": { + "node-git-hooks": "bin/install.js" + }, + "engines": { + "node": ">=4.0.0" + } + } + }, + "dependencies": { + "node-git-hooks": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-git-hooks/-/node-git-hooks-1.0.5.tgz", + "integrity": "sha512-05rULsJy8z2OvXTQFZv9fN20uo186EYgJYQjkL1OjnXj53QivOAGUzZilZ/MX8OmPRaN+deJBtlvMRydpdfnqA==" } } } diff --git a/package.json b/package.json index 16cf98d..e90c242 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,10 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "docs": "sasjs doc && ./sasjs/utils/build.sh" + "docs": "sasjs doc && ./sasjs/utils/build.sh", + "postinstall": "node-git-hooks" + }, + "dependencies": { + "node-git-hooks": "^1.0.5" } } From 28ea218d0294354cfc858b0d809169069f77790d Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sun, 21 Mar 2021 22:24:46 +0100 Subject: [PATCH 06/70] chore: adding pre-commit hook --- .githooks/pre-commit | 102 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 .githooks/pre-commit diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100644 index 0000000..91a5236 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,102 @@ +#!/bin/sh +# +# A hook script to verify that no filenames with capital letters are committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# Go through all the changed files (except for deleted and unmerged) +# +# Check for lines containing "debugger" "console." or "alert(" + +# Test only JS files +js_pattern="\.js" +# Check for changed lines +added_lines="^\+" +# Check for lines with comments +comment="(//|/\*).*" +beginning_of_line="^\+[\s]*" +# Check for lines containing "debugger" "console." or "alert(", skip comments +evil_pattern="(console\.|alert\(|debugger).*" +# Set exit code to 0, will be set to 1 on error. +exit_code=0 +# Grep git diff of files to commit +js_files=$( + git diff --cached --find-copies --find-renames --name-only --diff-filter=ACMRTXBU | + grep -E "$js_pattern" +) + + +if [[ -n $js_files ]]; +then + for file in $js_files; do + # Get only changed lines AND + # Only lines starting with '+' AND + # NOT lines with comments AND + # Test for console, debug etc. + lines_with_no_comment=$( + git diff --cached $file | + grep -E "$added_lines" | + grep -Ev "$comment" | + grep -E "$evil_pattern" + ) + # Get only changed lines AND + # Only lines starting with '+' AND + # NOT lines starting with a comment AND + # Test for lines with console, debug etc. before a comment + lines_with_comment=$( + git diff --cached $file | + grep -E "$added_lines" | + grep -Ev "$beginning_of_line$comment" | + grep -E "${evil_pattern}$comment" + ) + + + if [[ -n $lines_with_no_comment || -n $lines_with_comment ]]; + then + echo + echo -e "Found illegal commands in \033[1m$file:\033[0m" + echo -e '\E[0;32m'"$lines_with_no_comment" + echo -e "$lines_with_comment"'\033[0m' + echo + # Abort commit + exit_code=1 + fi + + done +fi + +# Check if file is an image JPG|GIF|PNG|JPEG and check for uppercase letters + +# Check for image file types +mime_pattern="\.(sas|ddl|csv|sh)" +# Check for capital letters only in file names +extra_pattern="(^|/)[^/]*([A-Z]+)[^/]*\.[A-Za-z]{3}$" +# Grep git diff of files to commit +files=$( git diff --cached --find-copies --find-renames --name-only --diff-filter=ACMRTXBU | + grep -Ei "$mime_pattern" | + grep -E "$extra_pattern" ) + +if [[ -n $files ]]; +then + echo + echo "Found files that contain capital letters." + echo "Please rename the following files and commit again:" + + for file in $files; do + echo -e '\E[0;32m'"$file"'\033[0m' + done + # Abort commit + exit_code=1 +fi + +if [ $exit_code == 0 ]; then + echo + echo -e '\033[1m'"Pre-commit validation Passed"'\033[0m' + echo +else + echo + echo -e '\033[1m'"Commit Aborted!"'\033[0m' + echo +fi +exit $exit_code \ No newline at end of file From 27b56e8efd7fdc2b45256db3af6d9a02c99907ad Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sun, 21 Mar 2021 23:33:19 +0100 Subject: [PATCH 07/70] set executable --- .githooks/pre-commit | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .githooks/pre-commit diff --git a/.githooks/pre-commit b/.githooks/pre-commit old mode 100644 new mode 100755 From db859bbf1d9753762bbf50981723cf40d53a8dc9 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sun, 21 Mar 2021 23:59:21 +0100 Subject: [PATCH 08/70] 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. --- .githooks/pre-commit | 70 ++++---------------------------------------- 1 file changed, 6 insertions(+), 64 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 91a5236..d680c22 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # A hook script to verify that no filenames with capital letters are committed. # Called by "git commit" with no arguments. The hook should @@ -6,69 +6,11 @@ # it wants to stop the commit. # # Go through all the changed files (except for deleted and unmerged) -# -# Check for lines containing "debugger" "console." or "alert(" -# Test only JS files -js_pattern="\.js" -# Check for changed lines -added_lines="^\+" -# Check for lines with comments -comment="(//|/\*).*" -beginning_of_line="^\+[\s]*" -# Check for lines containing "debugger" "console." or "alert(", skip comments -evil_pattern="(console\.|alert\(|debugger).*" -# Set exit code to 0, will be set to 1 on error. +# Save exit code of last executed action exit_code=0 -# Grep git diff of files to commit -js_files=$( - git diff --cached --find-copies --find-renames --name-only --diff-filter=ACMRTXBU | - grep -E "$js_pattern" -) - -if [[ -n $js_files ]]; -then - for file in $js_files; do - # Get only changed lines AND - # Only lines starting with '+' AND - # NOT lines with comments AND - # Test for console, debug etc. - lines_with_no_comment=$( - git diff --cached $file | - grep -E "$added_lines" | - grep -Ev "$comment" | - grep -E "$evil_pattern" - ) - # Get only changed lines AND - # Only lines starting with '+' AND - # NOT lines starting with a comment AND - # Test for lines with console, debug etc. before a comment - lines_with_comment=$( - git diff --cached $file | - grep -E "$added_lines" | - grep -Ev "$beginning_of_line$comment" | - grep -E "${evil_pattern}$comment" - ) - - - if [[ -n $lines_with_no_comment || -n $lines_with_comment ]]; - then - echo - echo -e "Found illegal commands in \033[1m$file:\033[0m" - echo -e '\E[0;32m'"$lines_with_no_comment" - echo -e "$lines_with_comment"'\033[0m' - echo - # Abort commit - exit_code=1 - fi - - done -fi - -# Check if file is an image JPG|GIF|PNG|JPEG and check for uppercase letters - -# Check for image file types +# Check if file is one of SAS|DDL|CSV|SH and check for uppercase letters mime_pattern="\.(sas|ddl|csv|sh)" # Check for capital letters only in file names extra_pattern="(^|/)[^/]*([A-Z]+)[^/]*\.[A-Za-z]{3}$" @@ -76,8 +18,8 @@ extra_pattern="(^|/)[^/]*([A-Z]+)[^/]*\.[A-Za-z]{3}$" files=$( git diff --cached --find-copies --find-renames --name-only --diff-filter=ACMRTXBU | grep -Ei "$mime_pattern" | grep -E "$extra_pattern" ) - -if [[ -n $files ]]; +echo "$files" +if [ "$files" !="" ]; then echo echo "Found files that contain capital letters." @@ -90,7 +32,7 @@ then exit_code=1 fi -if [ $exit_code == 0 ]; then +if [ "$exit_code" == "0" ]; then echo echo -e '\033[1m'"Pre-commit validation Passed"'\033[0m' echo From b7f5a2ec008449976733adac85638b79971bbadf Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Mon, 22 Mar 2021 10:14:52 +0100 Subject: [PATCH 09/70] chore: devops rules, updated gitignore and git hook --- .githooks/pre-commit | 6 +++--- .gitignore | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index d680c22..079896f 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -19,14 +19,14 @@ files=$( git diff --cached --find-copies --find-renames --name-only --diff-filte grep -Ei "$mime_pattern" | grep -E "$extra_pattern" ) echo "$files" -if [ "$files" !="" ]; +if [[ -n "$files" ]]; then echo echo "Found files that contain capital letters." - echo "Please rename the following files and commit again:" + echo "Please rename the following files in lowercase, and commit again:" for file in $files; do - echo -e '\E[0;32m'"$file"'\033[0m' + echo -e '- \E[0;32m'"$file"'\033[0m' done # Abort commit exit_code=1 diff --git a/.gitignore b/.gitignore index a6210ac..862fe0a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ node_modules sasjsbuild/ # avoid filenames with spaces being committed to source control -**\ ** \ No newline at end of file +**\ ** + +# ignore the mc_* files - containing macros for individual libraries +mc_* \ No newline at end of file From 81b75a32ed410cf1227f57a898fdc6b7ae9e7257 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Wed, 24 Mar 2021 18:34:58 +0000 Subject: [PATCH 10/70] chore: latest sasjs version --- .gitpod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitpod.yml b/.gitpod.yml index de29988..8b02b22 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -5,4 +5,4 @@ image: file: .gitpod.dockerfile vscode: extensions: - - sasjs.sasjs-for-vscode@1.4.3:LY5VLf6H5R3u7nqVRnCQRw== \ No newline at end of file + - sasjs.sasjs-for-vscode@1.6.0:V4hJpMtbpekMcPRNhh4SXQ== \ No newline at end of file From 5cdca95216686e924a4ab3f624e2e2320df86d57 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Wed, 24 Mar 2021 19:38:02 +0100 Subject: [PATCH 11/70] chore: vscode properties --- .vscode/.editorconfig | 9 +++++++++ .vscode/settings.json | 10 ++++++++++ 2 files changed, 19 insertions(+) create mode 100644 .vscode/.editorconfig create mode 100644 .vscode/settings.json diff --git a/.vscode/.editorconfig b/.vscode/.editorconfig new file mode 100644 index 0000000..831610e --- /dev/null +++ b/.vscode/.editorconfig @@ -0,0 +1,9 @@ +{ + "search.exclude": { + "**/sasjsbuild/**": true, + "**/dist/**":true + }, + "editor.insertSpaces": true, + "editor.tabSize": 2, + "trim_trailing_whitespace": true +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..475724d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "editor.tabSize": 2, + "editor.insertSpaces": true, + "editor.detectIndentation": true, + "editor.formatOnSave": true, + "editor.rulers": [ + 80 + ], + "files.trimTrailingWhitespace": true +} \ No newline at end of file From 35eadd0e9dcd9ba99f5abc9e69c0154eda731835 Mon Sep 17 00:00:00 2001 From: Allan Bowe <4420615+allanbowe@users.noreply.github.com> Date: Thu, 25 Mar 2021 15:45:28 +0100 Subject: [PATCH 12/70] Update README.md --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 684911b..00ce156 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; %inc mc; ``` -Documentation: https://sasjs.github.io/core.github.io/files.html +Documentation: https://core.sasjs.io # Components @@ -84,7 +84,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; ## File Properties - filenames much match macro names -- filenames must be lowercase +- filenames must be lowercase, without spaces - macro names must be lowercase - one macro per file - prefixes: @@ -99,7 +99,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; - unix style line endings (lf) - individual lines should be no more than 80 characters long - UTF-8 -- no trailing white space + ## Header Properties @@ -136,13 +136,15 @@ When contributing to this library, it is therefore important to ensure that all ## Coding Standards - 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 -- 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;` - 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. - 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 From 01a9a5b823ca0ebf118e3ed18749c217390be434 Mon Sep 17 00:00:00 2001 From: Allan Bowe <4420615+allanbowe@users.noreply.github.com> Date: Thu, 25 Mar 2021 20:09:24 +0100 Subject: [PATCH 13/70] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 00ce156..f98ba8e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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: From 200d9a5761af7c2911543a41bb0f6bfc4891a0a1 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Mon, 29 Mar 2021 23:02:17 +0200 Subject: [PATCH 14/70] chore: doxy updates --- package.json | 2 +- sasjs/doxy/Doxyfile | 21 +++++++++--------- sasjs/doxy/DoxygenLayout.xml | 2 +- sasjs/doxy/batch.bat | 6 ----- sasjs/doxy/doxygen.svg | 26 ++++++++++++++++++++++ sasjs/doxy/favicon.ico | Bin 1150 -> 2238 bytes sasjs/doxy/logo.png | Bin 0 -> 6620 bytes sasjs/doxy/new_footer.html | 5 +++-- sasjs/doxy/new_header.html | 42 ++++++----------------------------- sasjs/sasjsconfig.json | 8 ++++++- 10 files changed, 55 insertions(+), 57 deletions(-) delete mode 100644 sasjs/doxy/batch.bat create mode 100644 sasjs/doxy/doxygen.svg create mode 100644 sasjs/doxy/logo.png diff --git a/package.json b/package.json index e90c242..a16ffb5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sasjs/core", - "description": "SAS Macro Library for Application Development", + "description": "Production Ready Macros for SAS Application Developers", "license": "MIT", "keywords": [ "SAS", diff --git a/sasjs/doxy/Doxyfile b/sasjs/doxy/Doxyfile index 1ea8e28..7f54b7c 100644 --- a/sasjs/doxy/Doxyfile +++ b/sasjs/doxy/Doxyfile @@ -14,20 +14,19 @@ HIDE_SCOPE_NAMES = YES HIDE_UNDOC_CLASSES = YES HIDE_UNDOC_MEMBERS = YES HTML_OUTPUT = $(DOXY_HTML_OUTPUT) -HTML_HEADER = $(DOXY_CONTENT)new_header.html -HTML_FOOTER = $(DOXY_CONTENT)new_footer.html -HTML_EXTRA_STYLESHEET = $(DOXY_CONTENT)new_stylesheet.css +HTML_HEADER = $(HTML_HEADER) +HTML_EXTRA_FILES = $(HTML_EXTRA_FILES) +HTML_FOOTER = $(HTML_FOOTER) +HTML_EXTRA_STYLESHEET = $(HTML_EXTRA_STYLESHEET) INHERIT_DOCS = NO INLINE_INFO = NO -INPUT = $(DOXY_CONTENT)../../README.md \ - = $(DOXY_CONTENT)../../main.dox \ - $(DOXY_INPUT) +INPUT = $(DOXY_INPUT) +LAYOUT_FILE = $(LAYOUT_FILE) USE_MDFILE_AS_MAINPAGE = README.md -LAYOUT_FILE = $(DOXY_CONTENT)DoxygenLayout.xml MAX_INITIALIZER_LINES = 0 -PROJECT_NAME = Macro Core -PROJECT_LOGO = $(DOXY_CONTENT)Macro_core_website_1.png -PROJECT_BRIEF = "Production Ready Macros for SAS Application Developers" +PROJECT_NAME = $(PROJECT_NAME) +PROJECT_LOGO = $(PROJECT_LOGO) +PROJECT_BRIEF = $(PROJECT_BRIEF) RECURSIVE = YES REPEAT_BRIEF = NO SHOW_NAMESPACES = NO @@ -38,4 +37,4 @@ STRICT_PROTO_MATCHING = YES STRIP_CODE_COMMENTS = NO SUBGROUPING = NO TAB_SIZE = 2 -VERBATIM_HEADERS = NO +VERBATIM_HEADERS = NO \ No newline at end of file diff --git a/sasjs/doxy/DoxygenLayout.xml b/sasjs/doxy/DoxygenLayout.xml index 3d18413..05b7512 100644 --- a/sasjs/doxy/DoxygenLayout.xml +++ b/sasjs/doxy/DoxygenLayout.xml @@ -18,7 +18,7 @@ - + diff --git a/sasjs/doxy/batch.bat b/sasjs/doxy/batch.bat deleted file mode 100644 index 673c894..0000000 --- a/sasjs/doxy/batch.bat +++ /dev/null @@ -1,6 +0,0 @@ -ECHO on - -rmdir /s /q C:\DEVELOPMENT\output -mkdir C:\DEVELOPMENT\output - -doxygen.exe Doxyfile \ No newline at end of file diff --git a/sasjs/doxy/doxygen.svg b/sasjs/doxy/doxygen.svg new file mode 100644 index 0000000..46f4a19 --- /dev/null +++ b/sasjs/doxy/doxygen.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sasjs/doxy/favicon.ico b/sasjs/doxy/favicon.ico index 1cb2ca528470e107ed521f543106fa14de8cf83e..d8c7dcbd834428e6136e9f61e0151f2c98466fea 100644 GIT binary patch literal 2238 zcmeHHJ5Izf5Pgo5IKPb-3OdA=NJtz235k+|ej8dU+O4*vqobvw-G-Wmnt}r$aR9^x zH~=%YgCpltpp_6(CbFH(n;E}MUJMAj77NgvVUq!S01j!83uiRgZ!ETpnFY->^3@vk z;}epTQ|!EdVE+0BQEOyZ*NFBHpq7^?@9t6D+(JLUpnBLKJ->iFI>zqjSF7N3J9S{{ zz<=w&d~2q1T_nTRAhp&q8wi-igtC=UPlOT-(J{wMuEL0v-7zO-W|K4sAXLKo5^!t> zk3I3a4;QVBU*iyl*kC@f-+JnLnpoea$k>bk7tPP~ z!J7JR^*B4|TlM3m1qN{y)uZ{Y>R|pD?!~3L{8>Ad6<1Pl7Xtn%YgB@yVr3R;Sr#TM p2E8H4)Jw|MA&sJ9h)j4UKf4&e-6Sk+V(c%Hr-CVL%l`Vj`UbwiOVt1X literal 1150 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 literal 0 HcmV?d00001 diff --git a/sasjs/doxy/new_footer.html b/sasjs/doxy/new_footer.html index 1d66b15..f4c14e8 100644 --- a/sasjs/doxy/new_footer.html +++ b/sasjs/doxy/new_footer.html @@ -8,7 +8,7 @@ @@ -24,9 +24,10 @@ + diff --git a/sasjs/doxy/new_header.html b/sasjs/doxy/new_header.html index 030ebd8..16801cd 100644 --- a/sasjs/doxy/new_header.html +++ b/sasjs/doxy/new_header.html @@ -9,7 +9,7 @@ - $projectname: $title + $title @@ -19,7 +19,7 @@ $treeview $search $mathjax - + $extrastylesheet @@ -33,52 +33,24 @@ - Logo + Logo - -
-  $projectnumber -
-
Production Ready Macros for SAS Application Developers
https://github.com/sasjs/core
- - - - - - -
$projectbrief
- - - - - -
- Production Ready Macros for SAS Application Developers -
- - https://github.com/sasjs/core - -
- - + + $searchbox diff --git a/sasjs/sasjsconfig.json b/sasjs/sasjsconfig.json index 2f7e27b..cea7e36 100644 --- a/sasjs/sasjsconfig.json +++ b/sasjs/sasjsconfig.json @@ -2,6 +2,12 @@ "$schema": "https://cli.sasjs.io/sasjsconfig-schema.json", "macroFolders": ["base", "meta", "metax", "viya", "lua"], "docConfig":{ - "displayMacroCore": false + "displayMacroCore": false, + "enableLineage": false, + "doxyContent": { + "favIcon": "runningman.jpg", + "logo": "Macro_core_website_1.png", + "readMe": "../../README.md" + } } } From 2b6bf4bd02ef8742d3bf1f7ba62b56cd3f775dff Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Tue, 30 Mar 2021 00:01:08 +0200 Subject: [PATCH 15/70] chore: doxy fixes --- sasjs/doxy/Doxyfile | 1 + sasjs/doxy/DoxygenLayout.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sasjs/doxy/Doxyfile b/sasjs/doxy/Doxyfile index 7f54b7c..fcc2bd9 100644 --- a/sasjs/doxy/Doxyfile +++ b/sasjs/doxy/Doxyfile @@ -21,6 +21,7 @@ HTML_EXTRA_STYLESHEET = $(HTML_EXTRA_STYLESHEET) INHERIT_DOCS = NO INLINE_INFO = NO INPUT = $(DOXY_INPUT) +INPUT += main.dox LAYOUT_FILE = $(LAYOUT_FILE) USE_MDFILE_AS_MAINPAGE = README.md MAX_INITIALIZER_LINES = 0 diff --git a/sasjs/doxy/DoxygenLayout.xml b/sasjs/doxy/DoxygenLayout.xml index 05b7512..3d18413 100644 --- a/sasjs/doxy/DoxygenLayout.xml +++ b/sasjs/doxy/DoxygenLayout.xml @@ -18,7 +18,7 @@ - + From ae72446f850cd81f7f529c2221fa580deff2037e Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Tue, 30 Mar 2021 00:01:43 +0200 Subject: [PATCH 16/70] chore: updating all.sas --- all.sas | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/all.sas b/all.sas index 629b233..52c0a47 100644 --- a/all.sas +++ b/all.sas @@ -7601,6 +7601,98 @@ run; %return; %end; +%mend; +/** + @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 + +

SAS Macros

+ @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 ""; + put "SAS268436480"; + put ""; +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; /** @file mm_deletestp.sas @@ -8453,6 +8545,39 @@ run; options metarepository=&oldrepo; %end; +%mend;/** + @file + @brief Compares the metadata of a library with the physical tables + + @param [out] prefix the dataset to create that contains the list of libraries + @param mDebug set to anything but 0 to show debug messages in the log + + @test Create a temporary library as follows: + + % + + @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. + + + + + @version 9.3 + @author Allan Bowe + +**/ + +%macro mm_getlibmetadiffs + prefix=metadiff + ,mdebug=0 +)/*/STORE SOURCE*/; + +%if &mdebug = 0 %then %let mdebug=*; + +&mdebug %put _local_; + + %mend;/** @file @brief Creates a dataset with all metadata libraries From e3c333ea3950732dad77d258dc642dce35a66734 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Tue, 30 Mar 2021 00:02:20 +0200 Subject: [PATCH 17/70] feat: adding mm_deletelibrary.sas, a macro to easily delete a SAS Library from metadata. Documentation: https://core.sasjs.io/mm__deletelibrary_8sas.html --- meta/mm_deletelibrary.sas | 92 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 meta/mm_deletelibrary.sas diff --git a/meta/mm_deletelibrary.sas b/meta/mm_deletelibrary.sas new file mode 100644 index 0000000..2f760a2 --- /dev/null +++ b/meta/mm_deletelibrary.sas @@ -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 + +

SAS Macros

+ @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 ""; + put "SAS268436480"; + put ""; +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; From 539447ed06399aaa537d92047db7e44933f09436 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Tue, 30 Mar 2021 16:34:11 +0200 Subject: [PATCH 18/70] chore: fixing all.sas --- all.sas | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/all.sas b/all.sas index 52c0a47..53f9f50 100644 --- a/all.sas +++ b/all.sas @@ -8548,37 +8548,48 @@ run; %mend;/** @file @brief Compares the metadata of a library with the physical tables - - @param [out] prefix the dataset to create that contains the list of libraries - @param mDebug set to anything but 0 to show debug messages in the log - - @test Create a temporary library as follows: - - % - @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. - + @param [in] libname the metadata name of the library to be compared + @param [out] prefix the dataset to create that contains the list of libraries @version 9.3 @author Allan Bowe **/ -%macro mm_getlibmetadiffs +%macro mm_getlibmetadiffs( prefix=metadiff - ,mdebug=0 )/*/STORE SOURCE*/; -%if &mdebug = 0 %then %let mdebug=*; - -&mdebug %put _local_; -%mend;/** + options VALIDVARNAME=ANY VALIDMEMNAME=EXTEND; + + + ods trace on; + ods output + factoid1=s9h.&requestid.ml_all + updtab=s9h.&requestid.ml_up + addtab=s9h.&requestid.ml_add + deltab=s9h.&requestid.ml_del + ; + + proc metalib; + omr=(library="&libraryname"); + noexec; + report(type=detail); + run; + + ods output close; + ods trace off; + + +%mend; +/** @file @brief Creates a dataset with all metadata libraries @details Will only show the libraries to which a user has the requisite From 1b70205cab43fdce394048707ab1606012f3b44f Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Fri, 2 Apr 2021 13:38:46 +0200 Subject: [PATCH 19/70] 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) --- all.sas | 198 +++++++++++++++++++++++++++++++++--- base/mp_mdtablewrite.sas | 98 ++++++++++++++++++ meta/mm_getlibmetadiffs.sas | 129 +++++++++++++++++++++++ 3 files changed, 413 insertions(+), 12 deletions(-) create mode 100644 base/mp_mdtablewrite.sas create mode 100644 meta/mm_getlibmetadiffs.sas diff --git a/all.sas b/all.sas index 53f9f50..fcaba22 100644 --- a/all.sas +++ b/all.sas @@ -4093,6 +4093,103 @@ select distinct lowcase(memname) %end; %mend;/** + @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) + + +

SAS Macros

+ @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;/** @file @brief Logs the time the macro was executed in a control dataset. @details If the dataset does not exist, it is created. Usage: @@ -8552,9 +8649,76 @@ run; 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 - @param [in] libname the metadata name of the library to be compared - @param [out] prefix the dataset to create that contains the list of libraries + 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| + + @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 @@ -8562,33 +8726,43 @@ run; **/ %macro mm_getlibmetadiffs( - prefix=metadiff + 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 trace on; ods output - factoid1=s9h.&requestid.ml_all - updtab=s9h.&requestid.ml_up - addtab=s9h.&requestid.ml_add - deltab=s9h.&requestid.ml_del + factoid1=&outlib..&prefix.meta + updtab=&outlib..&prefix.updated + addtab=&outlib..&prefix.added + deltab=&outlib..&prefix.deleted ; proc metalib; - omr=(library="&libraryname"); + omr=(library="&libname"); noexec; report(type=detail); + update_rule (delete); run; ods output close; - ods trace off; + /* restore options */ + proc optload data=&tempds; + run; -%mend; +%mend mm_getlibmetadiffs; /** @file @brief Creates a dataset with all metadata libraries diff --git a/base/mp_mdtablewrite.sas b/base/mp_mdtablewrite.sas new file mode 100644 index 0000000..a5d3528 --- /dev/null +++ b/base/mp_mdtablewrite.sas @@ -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) + + +

SAS Macros

+ @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; \ No newline at end of file diff --git a/meta/mm_getlibmetadiffs.sas b/meta/mm_getlibmetadiffs.sas new file mode 100644 index 0000000..82af1d3 --- /dev/null +++ b/meta/mm_getlibmetadiffs.sas @@ -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; From 030c4a4fc110d8aeae11cb95bbad6644b6cb52e4 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sat, 3 Apr 2021 18:10:41 +0000 Subject: [PATCH 20/70] fix: .sasjslint file and indentation issues --- .gitpod.yml | 2 +- .sasjslint | 10 +++++++++ README.md | 6 +++--- viya/mv_getjoblog.sas | 7 ++++--- viya/mv_getrefreshtoken.sas | 34 +++++++++++++++--------------- viya/mv_getusergroups.sas | 2 +- viya/mv_getusers.sas | 10 ++++----- viya/mv_jobflow.sas | 20 +++++++++++------- viya/mv_jobwaitfor.sas | 2 +- viya/mv_registerclient.sas | 42 +++++++++++++++++++++++-------------- viya/mv_tokenauth.sas | 26 +++++++++++++---------- viya/mv_tokenrefresh.sas | 18 +++++++++------- viya/mv_webout.sas | 8 ++++--- 13 files changed, 110 insertions(+), 77 deletions(-) create mode 100644 .sasjslint diff --git a/.gitpod.yml b/.gitpod.yml index 8b02b22..549bf82 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -5,4 +5,4 @@ image: file: .gitpod.dockerfile vscode: extensions: - - sasjs.sasjs-for-vscode@1.6.0:V4hJpMtbpekMcPRNhh4SXQ== \ No newline at end of file + - sasjs.sasjs-for-vscode@1.7.2:R6y1nzpFh2P99BZg5FgH5g== \ No newline at end of file diff --git a/.sasjslint b/.sasjslint new file mode 100644 index 0000000..9de0cb1 --- /dev/null +++ b/.sasjslint @@ -0,0 +1,10 @@ +{ + "noTrailingSpaces": true, + "noEncodedPasswords": true, + "hasDoxygenHeader": true, + "noSpacesInFileNames": true, + "maxLineLength": 120, + "lowerCaseFileNames": true, + "noTabIndentation": true, + "indentationMultiple": 2 +} \ No newline at end of file diff --git a/README.md b/README.md index f98ba8e..94746be 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Documentation: https://core.sasjs.io - X command enabled - Prefixes: _mmw_,_mmu_,_mmx_ -**lua** library +**lua** library Wait - this is a macro library - what is LUA doing here? Well, it is a little known fact that you CAN run LUA within a SAS Macro. It has to be written to a text file with a `.lua` extension, from where you can `%include` it. So, without using the `proc lua` wrapper. @@ -51,7 +51,7 @@ To contribute, simply write your freeform LUA in the LUA folder. Then run the ` %ml_yourmodule() /* Execute. Do not use the restart keyword! */ -proc lua; +proc lua; submit; print(yourStuff); endsubmit; @@ -118,7 +118,7 @@ All macros must be commented in the doxygen format, to enable the [online docume ### 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

SAS Macros

@li mf_nobs.sas @li mm_assignlib.sas diff --git a/viya/mv_getjoblog.sas b/viya/mv_getjoblog.sas index 860928e..b72c553 100644 --- a/viya/mv_getjoblog.sas +++ b/viya/mv_getjoblog.sas @@ -10,7 +10,8 @@ First, compile the 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; Next, create a job (in this case, a web service): @@ -174,7 +175,7 @@ data _null_; outfile:write(logloc) io.close(infile) io.close(outfile) - '; + '; run; %inc "&fpath3..lua"; /* get log path*/ @@ -228,7 +229,7 @@ data _null_; io.input(infile) local resp=json.decode(io.read()) for i, v in pairs(resp["items"]) do - outfile:write(v.line,"\n") + outfile:write(v.line,"\n") end io.close(infile) io.close(outfile) diff --git a/viya/mv_getrefreshtoken.sas b/viya/mv_getrefreshtoken.sas index d7b7904..82ecf39 100644 --- a/viya/mv_getrefreshtoken.sas +++ b/viya/mv_getrefreshtoken.sas @@ -1,24 +1,24 @@ - /** - @file mv_getrefreshtoken.sas - @brief deprecated - replaced by mv_tokenauth.sas +/** + @file mv_getrefreshtoken.sas + @brief deprecated - replaced by mv_tokenauth.sas - @version VIYA V.03.04 - @author Allan Bowe, source: https://github.com/sasjs/core + @version VIYA V.03.04 + @author Allan Bowe, source: https://github.com/sasjs/core -

SAS Macros

- @li mv_tokenauth.sas +

SAS Macros

+ @li mv_tokenauth.sas - **/ +**/ - %macro mv_getrefreshtoken(client_id=someclient - ,client_secret=somesecret - ,grant_type=authorization_code - ,code= - ,user= - ,pass= - ,access_token_var=ACCESS_TOKEN - ,refresh_token_var=REFRESH_TOKEN - ); +%macro mv_getrefreshtoken(client_id=someclient + ,client_secret=somesecret + ,grant_type=authorization_code + ,code= + ,user= + ,pass= + ,access_token_var=ACCESS_TOKEN + ,refresh_token_var=REFRESH_TOKEN + ); %mv_tokenauth(client_id=&client_id ,client_secret=&client_secret diff --git a/viya/mv_getusergroups.sas b/viya/mv_getusergroups.sas index b67920c..72a4d21 100644 --- a/viya/mv_getusergroups.sas +++ b/viya/mv_getusergroups.sas @@ -65,7 +65,7 @@ proc http method='GET' out=&fname1 &oauth_bearer url="&base_uri/identities/users/&user/memberships?limit=10000"; headers %if &grant_type=authorization_code %then %do; - "Authorization"="Bearer &&&access_token_var" + "Authorization"="Bearer &&&access_token_var" %end; "Accept"="application/json"; run; diff --git a/viya/mv_getusers.sas b/viya/mv_getusers.sas index bef7bf4..935df83 100644 --- a/viya/mv_getusers.sas +++ b/viya/mv_getusers.sas @@ -37,11 +37,11 @@ @param access_token_var= The global macro variable to contain the access token @param grant_type= valid values: - * password - * authorization_code - * detect - will check if access_token exists, if not will use sas_services if - a SASStudioV session else authorization_code. Default option. - * sas_services - will use oauth_bearer=sas_services + * password + * authorization_code + * detect - will check if access_token exists, if not will use sas_services if + a SASStudioV session else authorization_code. Default option. + * sas_services - will use oauth_bearer=sas_services @param outds= The library.dataset to be created that contains the list of groups diff --git a/viya/mv_jobflow.sas b/viya/mv_jobflow.sas index 6b4142a..5f45d6b 100644 --- a/viya/mv_jobflow.sas +++ b/viya/mv_jobflow.sas @@ -13,7 +13,7 @@ @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`. + 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. @@ -97,15 +97,17 @@ run; - @param [in] access_token_var= The global macro variable to contain the access token + @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 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] 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 @@ -185,7 +187,7 @@ select count(*) into: missings where flow_id is null or _program is null; %mp_abort(iftrue=(&missings>0) ,mac=&sysmacroname - ,msg=%str(input dataset contains &missings missing values for FLOW_ID or _PROGRAM) + ,msg=%str(input dataset has &missings missing values for FLOW_ID or _PROGRAM) ) %if %mf_nobs(&inds)=0 %then %do; @@ -284,7 +286,8 @@ data;run;%let jdswaitfor=&syslast; %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); + %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 @@ -334,7 +337,8 @@ data;run;%let jdswaitfor=&syslast; /* 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; + %put looping flow &fid again - &completed of &jcnt jobs completed, + &concurrency jobs running; %end; %end; %end; diff --git a/viya/mv_jobwaitfor.sas b/viya/mv_jobwaitfor.sas index 9820903..4735f7c 100644 --- a/viya/mv_jobwaitfor.sas +++ b/viya/mv_jobwaitfor.sas @@ -225,7 +225,7 @@ run; %else %let SYSCC=5; %put %str(ERR)OR: Job &&jobname&i. did not complete successfully. &stateDetails; %return; - %end; + %end; %end; %end; diff --git a/viya/mv_registerclient.sas b/viya/mv_registerclient.sas index 9c2f585..a1fca07 100644 --- a/viya/mv_registerclient.sas +++ b/viya/mv_registerclient.sas @@ -4,15 +4,17 @@ @details When building apps on SAS Viya, an client id and secret is required. This macro will obtain the Consul Token and use that to call the Web Service. - more info: https://developer.sas.com/reference/auth/#register - and: http://proc-x.com/2019/01/authentication-to-sas-viya-a-couple-of-approaches/ + more info: https://developer.sas.com/reference/auth/#register + and: + http://proc-x.com/2019/01/authentication-to-sas-viya-a-couple-of-approaches The default viyaroot location is /opt/sas/viya/config Usage: %* 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; %* specific client with just openid scope; @@ -33,7 +35,8 @@ @param client_id= The client name. Auto generated if blank. @param client_secret= Client secret Auto generated if client is blank. @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 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) @@ -78,15 +81,16 @@ ,refresh_token_validity=DEFAULT ,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= Generated by %mf_getuser() on %sysfunc(datetime(),datetime19.) using SASjs; options noquotelenmax; /* first, get consul token needed to get client id / secret */ +%let tokloc=/etc/SASSecurityCertificateFramework/tokens/consul/default; data _null_; - infile "%mf_loc(VIYACONFIG)/etc/SASSecurityCertificateFramework/tokens/consul/default/client.token"; + infile "%mf_loc(VIYACONFIG)&tokloc/client.token"; input token:$64.; call symputx('consul_token',token); run; @@ -97,8 +101,9 @@ run; /* request the client details */ %let fname1=%mf_getuniquefileref(); proc http method='POST' out=&fname1 - url="&base_uri/SASLogon/oauth/clients/consul?callback=false%str(&)serviceId=app"; - headers "X-Consul-Token"="&consul_token"; + url="&base_uri/SASLogon/oauth/clients/consul?callback=false%str(&)%trim( + )serviceId=app"; + headers "X-Consul-Token"="&consul_token"; run; %let libref=%mf_getuniquelibref(); @@ -111,8 +116,8 @@ data _null_; run; /** - * register the new client - */ + * register the new client + */ %let fname2=%mf_getuniquefileref(); %if x&client_id.x=xx %then %do; %let client_id=client_%sysfunc(ranuni(0),hex16.); @@ -122,7 +127,8 @@ run; %let scopes=%sysfunc(coalescec(&scopes,openid)); %let scopes=%mf_getquotedstr(&scopes,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_; file &fname2; @@ -139,9 +145,11 @@ data _null_; if reqd_groups = '""' then reqd_groups =''; else reqd_groups=cats(',"required_user_groups":[',reqd_groups,']'); 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')); - 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 clientsecret ; @@ -206,10 +214,12 @@ run; %put GRANT_TYPE=&grant_type; %put; %if %index(%superq(grant_type),authorization_code) %then %do; - /* cannot use base_uri here as it includes the protocol which may be incorrect externally */ - %put NOTE: The developer must also register below and select 'openid' to get the grant code:; + /* cannot use base_uri here as it includes the protocol which may be incorrect + externally */ + %put NOTE: Visit the link below and select 'openid' to get the grant code:; %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- ; %end; diff --git a/viya/mv_tokenauth.sas b/viya/mv_tokenauth.sas index 4263683..47c2d58 100644 --- a/viya/mv_tokenauth.sas +++ b/viya/mv_tokenauth.sas @@ -15,7 +15,8 @@ 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; @@ -31,13 +32,15 @@ @param outds= A dataset containing access_token and refresh_token @param client_id= The client name @param client_secret= client secret - @param grant_type= valid values are "password" or "authorization_code" (unquoted). - The default is authorization_code. - @param code= If grant_type=authorization_code then provide the necessary code here + @param grant_type= valid values are "password" or "authorization_code" + (unquoted). The default is authorization_code. + @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 pass= If grant_type=password then provide the password here @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 @version VIYA V.03.04 @@ -88,7 +91,8 @@ ,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 ,msg=%str(username / password required) ) @@ -99,7 +103,7 @@ data _null_; file &fref1; if "&grant_type"='authorization_code' then string=cats( - 'grant_type=authorization_code&code=',symget('code')); + 'grant_type=authorization_code&code=',symget('code')); else string=cats('grant_type=password&username=',symget('user') ,'&password=',symget(pass)); call symputx('grantstring',cats("'",string,"'")); @@ -107,8 +111,8 @@ run; /*data _null_;infile &fref1;input;put _infile_;run;*/ /** - * Request access token - */ + * Request access token + */ %if &base_uri=#NOTSET# %then %let base_uri=%mf_getplatform(VIYARESTAPI); %let fref2=%mf_getuniquefileref(); @@ -123,8 +127,8 @@ run; /*data _null_;infile &fref2;input;put _infile_;run;*/ /** - * Extract access / refresh tokens - */ + * Extract access / refresh tokens + */ %let libref=%mf_getuniquelibref(); libname &libref JSON fileref=&fref2; diff --git a/viya/mv_tokenrefresh.sas b/viya/mv_tokenrefresh.sas index a3504bd..24977b7 100644 --- a/viya/mv_tokenrefresh.sas +++ b/viya/mv_tokenrefresh.sas @@ -32,12 +32,13 @@ @param outds= A dataset containing access_token and refresh_token @param client_id= The client name (alternative to inds) @param client_secret= client secret (alternative to inds) - @param grant_type= valid values are "password" or "authorization_code" (unquoted). - The default is authorization_code. + @param grant_type= valid values are "password" or "authorization_code" + (unquoted). The default is authorization_code. @param user= If grant_type=password then provide the username 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 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 @author Allan Bowe, source: https://github.com/sasjs/core @@ -72,7 +73,8 @@ options noquotelenmax; ,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 ,msg=%str(username / password required) ) @@ -92,8 +94,8 @@ options noquotelenmax; ) /** - * Request access token - */ + * Request access token + */ %local base_uri; /* location of rest apis */ %let base_uri=%mf_getplatform(VIYARESTAPI); @@ -111,8 +113,8 @@ run; /*data _null_;infile &fref1;input;put _infile_;run;*/ /** - * Extract access / refresh tokens - */ + * Extract access / refresh tokens + */ %let libref=%mf_getuniquelibref(); libname &libref JSON fileref=&fref1; diff --git a/viya/mv_webout.sas b/viya/mv_webout.sas index c02d1bb..44f965b 100644 --- a/viya/mv_webout.sas +++ b/viya/mv_webout.sas @@ -109,7 +109,8 @@ if _n_=1 then call symputx('input_statement',_infile_); list; data &table; - infile "%sysfunc(pathname(work))/&table..csv" firstobs=2 dsd termstr=crlf; + infile "%sysfunc(pathname(work))/&table..csv" firstobs=2 dsd + termstr=crlf; input &input_statement; run; %end; @@ -141,7 +142,7 @@ /* setup webout */ OPTIONS NOBOMFILE; %if "X&SYS_JES_JOB_URI.X"="XX" %then %do; - filename _webout temp lrecl=999999 mod; + filename _webout temp lrecl=999999 mod; %end; %else %do; filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" @@ -150,7 +151,8 @@ /* setup temp ref */ %if %upcase(&fref) ne _WEBOUT %then %do; - filename &fref temp lrecl=999999 permission='A::u::rwx,A::g::rw-,A::o::---' mod; + filename &fref temp lrecl=999999 permission='A::u::rwx,A::g::rw-,A::o::---' + mod; %end; /* setup json */ From 096bf4fa1161d3854ad0c1a1bb886504bf7e96e4 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sat, 3 Apr 2021 18:21:29 +0000 Subject: [PATCH 21/70] chore: more indentation fixes --- viya/mv_createjob.sas | 13 +++++++------ viya/mv_createwebservice.sas | 26 ++++++++++++++------------ viya/mv_getaccesstoken.sas | 34 +++++++++++++++++----------------- viya/mv_getapptoken.sas | 24 ++++++++++++------------ viya/mv_getjobcode.sas | 4 ++-- 5 files changed, 52 insertions(+), 49 deletions(-) diff --git a/viya/mv_createjob.sas b/viya/mv_createjob.sas index eeda795..0077b98 100644 --- a/viya/mv_createjob.sas +++ b/viya/mv_createjob.sas @@ -5,7 +5,8 @@ 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"; + 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; @@ -154,7 +155,7 @@ proc http method='GET' 'Accept'='application/vnd.sas.collection+json' 'Accept-Language'='string'; %if &debug=1 %then %do; - debug level = 3; + debug level = 3; %end; run; /*data _null_;infile &fname2;input;putlog _infile_;run;*/ @@ -191,13 +192,13 @@ data _null_; file &fname3 TERMSTR=' '; length string $32767; string=cats('{"version": 0,"name":"' - ,"&name" - ,'","type":"Compute","parameters":[{"name":"_addjesbeginendmacros"' + ,"&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}'); + ,context,',"type":"CHARACTER","label":"Context Name","required": false}'); end; string=cats(string,'],"code":"'); put string; @@ -267,7 +268,7 @@ proc http method='POST' %end; "Accept"="application/vnd.sas.job.definition+json"; %if &debug=1 %then %do; - debug level = 3; + debug level = 3; %end; run; /*data _null_;infile &fname4;input;putlog _infile_;run;*/ diff --git a/viya/mv_createwebservice.sas b/viya/mv_createwebservice.sas index 796a04f..7f7b96b 100644 --- a/viya/mv_createwebservice.sas +++ b/viya/mv_createwebservice.sas @@ -5,7 +5,8 @@ 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"; + filename mc url + "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; %inc mc; %* Step 2 - Create some code and add it to a web service; @@ -18,7 +19,7 @@ run; %* send data back; %webout(OPEN) - %webout(ARR,example1) * Array format, fast, suitable for large tables ; + %webout(ARR,example1) * Array format, fast, suitable for large tables; %webout(OBJ,example2) * Object format, easier to work with ; %webout(CLOSE) ;;;; @@ -52,7 +53,8 @@ adapter, add a (different) fileref here. @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 + 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, source: https://github.com/sasjs/core @@ -163,7 +165,7 @@ proc http method='GET' 'Accept'='application/vnd.sas.collection+json' 'Accept-Language'='string'; %if &debug=1 %then %do; - debug level = 3; + debug level = 3; %end; run; /*data _null_;infile &fname2;input;putlog _infile_;run;*/ @@ -200,23 +202,23 @@ data _null_; file &fname3 TERMSTR=' '; length string $32767; string=cats('{"version": 0,"name":"' - ,"&name" - ,'","type":"Compute","parameters":[{"name":"_addjesbeginendmacros"' + ,"&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}'); + ,context,',"type":"CHARACTER","label":"Context Name","required": false}'); end; string=cats(string,'],"code":"'); put string; run; /** - * Add webout macro - * These put statements are auto generated - to change the macro, change the - * source (mv_webout) and run `build.py` - */ + * Add webout macro + * These put statements are auto generated - to change the macro, change the + * source (mv_webout) and run `build.py` + */ filename sasjs temp lrecl=3000; data _null_; file sasjs; @@ -616,7 +618,7 @@ proc http method='POST' %end; "Accept"="application/vnd.sas.job.definition+json"; %if &debug=1 %then %do; - debug level = 3; + debug level = 3; %end; run; /*data _null_;infile &fname4;input;putlog _infile_;run;*/ diff --git a/viya/mv_getaccesstoken.sas b/viya/mv_getaccesstoken.sas index 2fc43c1..6fba6e3 100644 --- a/viya/mv_getaccesstoken.sas +++ b/viya/mv_getaccesstoken.sas @@ -1,24 +1,24 @@ - /** - @file mv_getaccesstoken.sas - @brief deprecated - replaced by mv_tokenrefresh.sas +/** + @file mv_getaccesstoken.sas + @brief deprecated - replaced by mv_tokenrefresh.sas - @version VIYA V.03.04 - @author Allan Bowe, source: https://github.com/sasjs/core + @version VIYA V.03.04 + @author Allan Bowe, source: https://github.com/sasjs/core -

SAS Macros

- @li mv_tokenrefresh.sas +

SAS Macros

+ @li mv_tokenrefresh.sas - **/ +**/ - %macro mv_getaccesstoken(client_id=someclient - ,client_secret=somesecret - ,grant_type=authorization_code - ,code= - ,user= - ,pass= - ,access_token_var=ACCESS_TOKEN - ,refresh_token_var=REFRESH_TOKEN - ); +%macro mv_getaccesstoken(client_id=someclient + ,client_secret=somesecret + ,grant_type=authorization_code + ,code= + ,user= + ,pass= + ,access_token_var=ACCESS_TOKEN + ,refresh_token_var=REFRESH_TOKEN + ); %mv_tokenrefresh(client_id=&client_id ,client_secret=&client_secret diff --git a/viya/mv_getapptoken.sas b/viya/mv_getapptoken.sas index 858a033..96a8d54 100644 --- a/viya/mv_getapptoken.sas +++ b/viya/mv_getapptoken.sas @@ -1,19 +1,19 @@ - /** - @file - @brief deprecated - replaced by mv_registerclient.sas +/** + @file + @brief deprecated - replaced by mv_registerclient.sas - @version VIYA V.03.04 - @author Allan Bowe, source: https://github.com/sasjs/core + @version VIYA V.03.04 + @author Allan Bowe, source: https://github.com/sasjs/core -

SAS Macros

- @li mv_registerclient.sas +

SAS Macros

+ @li mv_registerclient.sas - **/ +**/ - %macro mv_getapptoken(client_id=someclient - ,client_secret=somesecret - ,grant_type=authorization_code - ); +%macro mv_getapptoken(client_id=someclient + ,client_secret=somesecret + ,grant_type=authorization_code + ); %mv_registerclient(client_id=&client_id ,client_secret=&client_secret diff --git a/viya/mv_getjobcode.sas b/viya/mv_getjobcode.sas index e01a339..0c4b679 100644 --- a/viya/mv_getjobcode.sas +++ b/viya/mv_getjobcode.sas @@ -5,7 +5,7 @@ Example: %mv_getjobcode( - path=/Public/jobs + path=/Public/jobs ,name=some_job ,outfile=/tmp/some_job.sas ) @@ -129,7 +129,7 @@ data _null_; outfile:write(job) io.close(infile) io.close(outfile) - '; + '; run; %inc "&fpath3..lua"; /* export to desired destination */ From 0a38056c69872bb6c4ef63a4cbeca675f7ee93a5 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sat, 3 Apr 2021 21:30:51 +0200 Subject: [PATCH 22/70] fix: linting --- .githooks/pre-commit | 34 +- .sasjslint | 2 +- all.sas | 1734 ++++++++++++++++------------- base/mf_abort.sas | 22 +- base/mf_getattrc.sas | 2 +- base/mf_getattrn.sas | 4 +- base/mf_getengine.sas | 6 +- base/mf_getfilesize.sas | 2 +- base/mf_getkeyvalue.sas | 2 +- base/mf_getplatform.sas | 2 +- base/mf_getuser.sas | 4 +- base/mf_getvalue.sas | 2 +- base/mf_getvarformat.sas | 12 +- base/mf_getvarlen.sas | 8 +- base/mf_getvarnum.sas | 8 +- base/mf_getvartype.sas | 8 +- base/mf_isblank.sas | 9 +- base/mf_isdir.sas | 23 +- base/mf_mkdir.sas | 10 +- base/mf_mval.sas | 5 +- base/mf_trimstr.sas | 5 +- base/mf_verifymacvars.sas | 2 +- base/mf_wordsinstr1butnotstr2.sas | 6 +- base/mp_abort.sas | 27 +- base/mp_binarycopy.sas | 39 +- base/mp_cleancsv.sas | 16 +- base/mp_createconstraints.sas | 4 +- base/mp_createwebservice.sas | 2 +- base/mp_csv2ds.sas | 6 +- base/mp_deleteconstraints.sas | 2 +- base/mp_dirlist.sas | 41 +- base/mp_distinctfmtvalues.sas | 6 +- base/mp_dropmembers.sas | 2 +- base/mp_ds2cards.sas | 54 +- base/mp_ds2csv.sas | 28 +- base/mp_getconstraints.sas | 6 +- base/mp_getdbml.sas | 27 +- base/mp_getddl.sas | 47 +- base/mp_getmaxvarlengths.sas | 13 +- base/mp_jsonout.sas | 28 +- base/mp_prevobs.sas | 40 +- base/mp_recursivejoin.sas | 6 +- base/mp_runddl.sas | 12 +- base/mp_searchcols.sas | 2 +- base/mp_searchdata.sas | 23 +- base/mp_setkeyvalue.sas | 2 +- base/mp_stprequests.sas | 5 +- base/mp_streamfile.sas | 13 +- base/mp_testjob.sas | 2 +- base/mp_testwritespeedlibrary.sas | 4 +- base/mp_tree.sas | 21 +- meta/mm_assigndirectlib.sas | 71 +- meta/mm_assignlib.sas | 5 +- meta/mm_createapplication.sas | 16 +- meta/mm_createdocument.sas | 12 +- meta/mm_createfolder.sas | 45 +- meta/mm_createlibrary.sas | 52 +- meta/mm_createstp.sas | 33 +- meta/mm_createwebservice.sas | 16 +- meta/mm_deletedocument.sas | 18 +- meta/mm_deletestp.sas | 18 +- meta/mm_getauthinfo.sas | 12 +- meta/mm_getcols.sas | 2 +- meta/mm_getdirectories.sas | 14 +- meta/mm_getdocument.sas | 96 +- meta/mm_getfoldermembers.sas | 15 +- meta/mm_getfoldertree.sas | 5 +- meta/mm_getgroupmembers.sas | 6 +- meta/mm_getgroups.sas | 14 +- meta/mm_getlibs.sas | 2 +- meta/mm_getobjects.sas | 11 +- meta/mm_getpublictypes.sas | 5 +- meta/mm_getrepos.sas | 49 +- meta/mm_getroles.sas | 26 +- meta/mm_getstpcode.sas | 88 +- meta/mm_getstps.sas | 19 +- meta/mm_gettableid.sas | 6 +- meta/mm_gettree.sas | 6 +- meta/mm_gettypes.sas | 20 +- meta/mm_getusers.sas | 23 +- meta/mm_getwebappsrvprops.sas | 34 +- meta/mm_spkexport.sas | 14 +- meta/mm_tree.sas | 46 +- meta/mm_updatestpservertype.sas | 11 +- meta/mm_webout.sas | 4 +- metax/mmx_spkexport.sas | 8 +- sasjs/doxy/new_stylesheet.css | 4 +- viya/mv_createwebservice.sas | 14 +- 88 files changed, 1762 insertions(+), 1468 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 079896f..161b5b6 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -15,30 +15,30 @@ mime_pattern="\.(sas|ddl|csv|sh)" # Check for capital letters only in file names extra_pattern="(^|/)[^/]*([A-Z]+)[^/]*\.[A-Za-z]{3}$" # Grep git diff of files to commit -files=$( git diff --cached --find-copies --find-renames --name-only --diff-filter=ACMRTXBU | - grep -Ei "$mime_pattern" | - grep -E "$extra_pattern" ) +files=$( git diff --cached --find-copies --find-renames --name-only --diff-filter=ACMRTXBU | + grep -Ei "$mime_pattern" | + grep -E "$extra_pattern" ) echo "$files" if [[ -n "$files" ]]; then - echo - echo "Found files that contain capital letters." - echo "Please rename the following files in lowercase, and commit again:" + echo + echo "Found files that contain capital letters." + echo "Please rename the following files in lowercase, and commit again:" - for file in $files; do - echo -e '- \E[0;32m'"$file"'\033[0m' - done - # Abort commit - exit_code=1 + for file in $files; do + echo -e '- \E[0;32m'"$file"'\033[0m' + done + # Abort commit + exit_code=1 fi if [ "$exit_code" == "0" ]; then - echo - echo -e '\033[1m'"Pre-commit validation Passed"'\033[0m' - echo + echo + echo -e '\033[1m'"Pre-commit validation Passed"'\033[0m' + echo else - echo - echo -e '\033[1m'"Commit Aborted!"'\033[0m' - echo + echo + echo -e '\033[1m'"Commit Aborted!"'\033[0m' + echo fi exit $exit_code \ No newline at end of file diff --git a/.sasjslint b/.sasjslint index 9de0cb1..a4cc7cb 100644 --- a/.sasjslint +++ b/.sasjslint @@ -3,7 +3,7 @@ "noEncodedPasswords": true, "hasDoxygenHeader": true, "noSpacesInFileNames": true, - "maxLineLength": 120, + "maxLineLength": 140, "lowerCaseFileNames": true, "noTabIndentation": true, "indentationMultiple": 2 diff --git a/all.sas b/all.sas index fcaba22..7f97468 100644 --- a/all.sas +++ b/all.sas @@ -21,7 +21,7 @@ options noquotelenmax; @brief abort gracefully according to context @details Do not use directly! See bottom of explanation for details. - Configures an abort mechanism according to site specific policies or the + Configures an abort mechanism according to site specific policies or the particulars of an environment. For instance, can stream custom results back to the client in an STP Web App context, or completely stop in the case of a batch run. @@ -67,7 +67,10 @@ options noquotelenmax; input; putlog _infile_; i=1; 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_); logonce+1; end; @@ -130,21 +133,22 @@ options noquotelenmax; %let syscc=0; %if %symexist(SYS_JES_JOB_URI) %then %do; /* 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)); %end; %else %do; data _null_; if symexist('sysprocessmode') - then if symget("sysprocessmode")="SAS Stored Process Server" - then rc=stpsrvset('program error', 0); + then if symget("sysprocessmode")="SAS Stored Process Server" + then rc=stpsrvset('program error', 0); run; %end; /** - * endsas is reliable but kills some deployments. - * Abort variants are ungraceful (non zero return code) - * This approach lets SAS run silently until the end :-) - */ + * endsas is reliable but kills some deployments. + * Abort variants are ungraceful (non zero return code) + * This approach lets SAS run silently until the end :-) + */ %put _all_; filename skip temp; data _null_; @@ -367,7 +371,7 @@ options noquotelenmax; **/ %macro mf_getattrc( - libds + libds ,attr )/*/STORE SOURCE*/; %local dsid rc; @@ -391,7 +395,7 @@ options noquotelenmax; @param libds library.dataset @param attr Common values are NLOBS and NVARS, full list in [documentation]( - http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212040.htm) + http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212040.htm) @return output returns result of the attrn value supplied, or -1 and log message if error. @@ -400,7 +404,7 @@ options noquotelenmax; **/ %macro mf_getattrn( - libds + libds ,attr )/*/STORE SOURCE*/; %local dsid rc; @@ -448,7 +452,9 @@ options noquotelenmax; /* in case the parameter is a libref.tablename, pull off just the libref */ %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; %let engnum=%sysfunc(varnum(&dsid,ENGINE)); %let rc=%sysfunc(fetch(&dsid)); @@ -457,7 +463,7 @@ options noquotelenmax; %let rc= %sysfunc(close(&dsid)); %end; - &engine + &engine %mend; @@ -501,7 +507,7 @@ options noquotelenmax; %let rc=%sysfunc(filename(fref)); %if &format=NO %then %do; - &bytes + &bytes %end; %else %do; %sysfunc(INPUTN(&bytes, best.),sizekmg.) @@ -525,7 +531,7 @@ options noquotelenmax; %macro mf_getkeyvalue(key,libds=work.mp_setkeyvalue )/*/STORE SOURCE*/; - %local ds dsid key valc valn type rc; +%local ds dsid key valc valn type rc; %let dsid=%sysfunc(open(&libds(where=(key="&key")))); %syscall set(dsid); %let rc = %sysfunc(fetch(&dsid)); @@ -563,7 +569,7 @@ options noquotelenmax; %local a b c; %if &switch.NONE=NONE %then %do; %if %symexist(sysprocessmode) %then %do; - %if "&sysprocessmode"="SAS Object Server" + %if "&sysprocessmode"="SAS Object Server" or "&sysprocessmode"= "SAS Compute Server" %then %do; SASVIYA %end; @@ -795,7 +801,7 @@ options noquotelenmax; @file @brief Returns a userid according to session context @details In a workspace session, a user is generally represented by - &sysuserid or SYS_COMPUTE_SESSION_OWNER if it exists. + &sysuserid or SYS_COMPUTE_SESSION_OWNER if it exists. In a Stored Process session, &sysuserid resolves to a system account (default=sassrv) and instead there are several metadata username variables to choose from (_metauser, _metaperson @@ -805,7 +811,7 @@ options noquotelenmax; %let user= %mf_getUser(); %put &user; - + @param type - do not use, may be deprecated in a future release @return SYSUSERID (if workspace server) @@ -855,7 +861,7 @@ options noquotelenmax; %macro mf_getvalue(libds,variable,filter=1 )/*/STORE SOURCE*/; - %if %mf_getattrn(&libds,NLOBS)>0 %then %do; + %if %mf_getattrn(&libds,NLOBS)>0 %then %do; %local dsid rc &variable; %let dsid=%sysfunc(open(&libds(where=(&filter)))); %syscall set(dsid); @@ -903,9 +909,9 @@ options noquotelenmax; Usage: data test; - format str1 $1. num1 datetime19.; - str2='hello mum!'; num2=666; - stop; + format str1 $1. num1 datetime19.; + str2='hello mum!'; num2=666; + stop; run; %put %mf_getVarFormat(test,str1); %put %mf_getVarFormat(work.test,num1); @@ -943,9 +949,9 @@ options noquotelenmax; /* Get variable format */ %if(&vnum > 0) %then %let vformat=%sysfunc(varfmt(&dsid, &vnum)); %else %do; - %put NOTE: Variable &var does not exist in &libds; - %let rc = %sysfunc(close(&dsid)); - %return; + %put NOTE: Variable &var does not exist in &libds; + %let rc = %sysfunc(close(&dsid)); + %return; %end; %end; %else %do; @@ -973,8 +979,8 @@ options noquotelenmax; Usage: data test; - format str $1. num datetime19.; - stop; + format str $1. num datetime19.; + stop; run; %put %mf_getVarLen(test,str); %put %mf_getVarLen(work.test,num); @@ -1007,8 +1013,8 @@ options noquotelenmax; /* Get variable format */ %if(&vnum > 0) %then %let vlen = %sysfunc(varlen(&dsid, &vnum)); %else %do; - %put NOTE: Variable &var does not exist in &libds; - %let vlen = %str( ); + %put NOTE: Variable &var does not exist in &libds; + %let vlen = %str( ); %end; %end; %else %put dataset &libds not opened! (rc=&dsid); @@ -1088,8 +1094,8 @@ options noquotelenmax; Usage: data work.test; - format str $1. num datetime19.; - stop; + format str $1. num datetime19.; + stop; run; %put %mf_getVarNum(work.test,str); %put %mf_getVarNum(work.test,num); @@ -1121,8 +1127,8 @@ returns: /* Get variable number */ %let vnum = %sysfunc(varnum(&dsid, &var)); %if(&vnum <= 0) %then %do; - %put NOTE: Variable &var does not exist in &libds; - %let vnum = %str( ); + %put NOTE: Variable &var does not exist in &libds; + %let vnum = %str( ); %end; %end; %else %put dataset &ds not opened! (rc=&dsid); @@ -1140,8 +1146,8 @@ returns: Usage: data test; - length str $1. num 8.; - stop; + length str $1. num 8.; + stop; run; %put %mf_getvartype(test,str); %put %mf_getvartype(work.test,num); @@ -1170,8 +1176,8 @@ Usage: /* Get variable type (C/N) */ %if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.)); %else %do; - %put NOTE: Variable &var does not exist in &libds; - %let vtype = %str( ); + %put NOTE: Variable &var does not exist in &libds; + %let vtype = %str( ); %end; %end; %else %put dataset &libds not opened! (rc=&dsid); @@ -1188,12 +1194,13 @@ Usage: %sysevalf(%superq(param)=,boolean) Usage: - - %put mf_isblank(&var); - inspiration: https://support.sas.com/resources/papers/proceedings09/022-2009.pdf + %put mf_isblank(&var); - @param param VALUE to be checked + inspiration: + https://support.sas.com/resources/papers/proceedings09/022-2009.pdf + + @param param VALUE to be checked @return output returns 1 (if blank) else 0 @@ -1213,7 +1220,8 @@ Usage: %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 @@ -1224,18 +1232,18 @@ Usage: %macro mf_isdir(path )/*/STORE SOURCE*/; - %local rc did is_directory fref_t; + %local rc did is_directory fref_t; - %let is_directory = 0; - %let rc = %sysfunc(filename(fref_t, %superq(path))); - %let did = %sysfunc(dopen(&fref_t.)); - %if &did. ^= 0 %then %do; - %let is_directory = 1; - %let rc = %sysfunc(dclose(&did.)); - %end; - %let rc = %sysfunc(filename(fref_t)); + %let is_directory = 0; + %let rc = %sysfunc(filename(fref_t, %superq(path))); + %let did = %sysfunc(dopen(&fref_t.)); + %if &did. ^= 0 %then %do; + %let is_directory = 1; + %let rc = %sysfunc(dclose(&did.)); + %end; + %let rc = %sysfunc(filename(fref_t)); - &is_directory + &is_directory %mend;/** @file @@ -1314,8 +1322,8 @@ Usage: */ %if (%length(&dir) gt %length(&child)) %then %do; - %let parent = %substr(&dir, 1, %length(&dir)-%length(&child)); - %mf_mkdir(&parent) + %let parent = %substr(&dir, 1, %length(&dir)-%length(&child)); + %mf_mkdir(&parent) %end; /* @@ -1324,11 +1332,11 @@ Usage: %let dname = %sysfunc(dcreate(&child, &parent)); %if (%bquote(&dname) eq ) %then %do; - %put %str(ERR)OR: could not create &parent + &child; - %abort cancel; + %put %str(ERR)OR: could not create &parent + &child; + %abort cancel; %end; %else %do; - %put Directory created: &dir; + %put Directory created: &dir; %end; %end; /* exit quietly if directory did exist.*/ @@ -1336,8 +1344,9 @@ Usage: /** @file mf_mval.sas @brief Returns a macro variable value if the variable exists - @details Use this macro to avoid repetitive use of `%if %symexist(MACVAR) %then` - type logic. + @details + Use this macro to avoid repetitive use of `%if %symexist(MACVAR) %then` + type logic. Usage: %if %mf_mval(maynotexist)=itdid %then %do; @@ -1379,7 +1388,7 @@ Usage: %mend;/** @file mf_trimstr.sas @brief Removes character(s) from the end, if they exist - @details If the designated characters exist at the end of the string, they + @details If the designated characters exist at the end of the string, they are removed %put %mf_trimstr(/blah/,/); * /blah; @@ -1390,7 +1399,8 @@ Usage: @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 @@ -1481,7 +1491,7 @@ Usage: %macro mf_verifymacvars( - verifyVars /* list of macro variable NAMES */ + verifyVars /* list of macro variable NAMES */ ,makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */ ,mAbort=SOFT )/*/STORE SOURCE*/; @@ -1517,7 +1527,7 @@ Usage: Usage: %let x= %mf_wordsInStr1ButNotStr2( - Str1=blah sss blaaah brah bram boo + Str1=blah sss blaaah brah bram boo ,Str2= blah blaaah brah ssss ); @@ -1535,8 +1545,8 @@ Usage: **/ %macro mf_wordsInStr1ButNotStr2( - Str1= /* string containing words to extract */ - ,Str2= /* used to compare with the extract string */ + Str1= /* string containing words to extract */ + ,Str2= /* used to compare with the extract string */ )/*/STORE SOURCE*/; %local count_base count_extr i i2 extr_word base_word match outvar; @@ -1571,11 +1581,11 @@ Usage: results back to the client in an STP Web App context, or completely stop in the case of a batch run. - Using SAS Abort Cancel mechanisms can cause hung sessions in some Stored Process - environments. This macro takes a unique approach - we set the SAS syscc to 0, - run `stpsrvset('program error', 0)` (if SAS 9) and then - we open a macro - but don't close it! This provides a graceful abort for SAS web services in all - web enabled environments. + Using SAS Abort Cancel mechanisms can cause hung sessions in some Stored + Process environments. This macro takes a unique approach - we set the SAS + syscc to 0, run `stpsrvset('program error', 0)` (if SAS 9) and then - we open + a macro but don't close it! This provides a graceful abort for SAS web + services in all web enabled environments. @param mac= to contain the name of the calling macro @param msg= message to be returned @@ -1613,7 +1623,10 @@ Usage: input; putlog _infile_; i=1; 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_); logonce+1; end; @@ -1694,15 +1707,15 @@ Usage: %if %symexist(_metaport) %then %do; data _null_; if symexist('sysprocessmode') - then if symget("sysprocessmode")="SAS Stored Process Server" - then rc=stpsrvset('program error', 0); + then if symget("sysprocessmode")="SAS Stored Process Server" + then rc=stpsrvset('program error', 0); run; %end; /** - * endsas is reliable but kills some deployments. - * Abort variants are ungraceful (non zero return code) - * This approach lets SAS run silently until the end :-) - */ + * endsas is reliable but kills some deployments. + * Abort variants are ungraceful (non zero return code) + * This approach lets SAS run silently until the end :-) + */ %put _all_; filename skip temp; data _null_; @@ -1721,9 +1734,10 @@ Usage: @file @brief Copy any file using binary input / output streams @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 - default filerefs can be modified. - Based on http://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file + os-independent method to copy files. In case of naming collision, the + default filerefs can be modified. + Based on: + https://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file %mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout) @@ -1738,12 +1752,12 @@ Usage: **/ %macro mp_binarycopy( - inloc= /* full path and filename of the object to be copied */ + inloc= /* full path and filename of the object to be copied */ ,outloc= /* full path and filename of object to be created */ ,inref=____in /* override default to use own filerefs */ ,outref=____out /* override default to use own filerefs */ )/*/STORE SOURCE*/; - /* these IN and OUT filerefs can point to anything */ + /* these IN and OUT filerefs can point to anything */ %if &inref = ____in %then %do; filename &inref &inloc lrecl=1048576 ; %end; @@ -1751,20 +1765,20 @@ Usage: filename &outref &outloc lrecl=1048576 ; %end; - /* copy the file byte-for-byte */ - data _null_; - length filein 8 fileid 8; - filein = fopen("&inref",'I',1,'B'); - fileid = fopen("&outref",'O',1,'B'); - rec = '20'x; - do while(fread(filein)=0); - rc = fget(filein,rec,1); - rc = fput(fileid, rec); - rc =fwrite(fileid); - end; - rc = fclose(filein); - rc = fclose(fileid); - run; + /* copy the file byte-for-byte */ + data _null_; + length filein 8 fileid 8; + filein = fopen("&inref",'I',1,'B'); + fileid = fopen("&outref",'O',1,'B'); + rec = '20'x; + do while(fread(filein)=0); + rc = fget(filein,rec,1); + rc = fput(fileid, rec); + rc =fwrite(fileid); + end; + rc = fclose(filein); + rc = fclose(fileid); + run; %if &inref = ____in %then %do; filename &inref clear; %end; @@ -1775,10 +1789,10 @@ Usage: @file mp_cleancsv.sas @brief Fixes embedded cr / lf / crlf in CSV @details CSVs will sometimes contain lf or crlf within quotes (eg when - saved by excel). When the termstr is ALSO lf or crlf that can be tricky - to process using SAS defaults. - This macro converts any csv to follow the convention of a windows excel file, - applying CRLF line endings and converting embedded cr and crlf to lf. + saved by excel). When the termstr is ALSO lf or crlf that can be tricky + to process using SAS defaults. + This macro converts any csv to follow the convention of a windows excel file, + applying CRLF line endings and converting embedded cr and crlf to lf. usage: @@ -1796,7 +1810,7 @@ Usage: %macro mp_cleancsv(in=NOTPROVIDED,out=NOTPROVIDED,qchar='22'x); %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; %end; @@ -1805,9 +1819,9 @@ Usage: %if %index(&out,.) %then %let out="&out"; /** - * convert all cr and crlf within quotes to lf - * convert all other cr or lf to crlf - */ + * convert all cr and crlf within quotes to lf + * convert all other cr or lf to crlf + */ data _null_; infile &in recfm=n ; file &out recfm=n; @@ -1856,7 +1870,7 @@ Usage: constraint unq unique(tx_from, dd_type), constraint nnn not null(DD_SHORTDESC) ); - + %mp_getconstraints(lib=work,ds=example,outds=work.constraints) %mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES) %mp_createconstraints(inds=work.constraints,outds=created,execute=YES) @@ -1891,7 +1905,7 @@ data &outds; else type=constraint_type; create_statement=catx(" ","alter table",libref,".",table_name ,"add constraint",constraint_name,type,"("); - if last.constraint_name then + if last.constraint_name then create_statement=cats(create_statement,column_name,");"); else create_statement=cats(create_statement,column_name,","); if "&execute"="YES" then call execute(create_statement); @@ -1923,7 +1937,7 @@ Usage: filename ft15f001 temp; parmcards4; %* fetch any data from frontend ; - %webout(FETCH) + %webout(FETCH) data example1 example2; set sashelp.class; run; @@ -2054,8 +2068,8 @@ Usage: %local hasheader; %let hasheader=0; data _null_; if _N_ > 1 then do; - call symputx('hasheader',1,'l'); - stop; + call symputx('hasheader',1,'l'); + stop; end; infile &inref; input; @@ -2123,7 +2137,7 @@ run; /* import the CSV */ data &outds %if %upcase(&view)=YES %then %do; - /view=&outds + /view=&outds %end; ; infile &inref dsd firstobs=2; @@ -2148,7 +2162,7 @@ run; constraint unq unique(tx_from, dd_type), constraint nnn not null(DD_SHORTDESC) ); - + %mp_getconstraints(lib=work,ds=example,outds=work.constraints) %mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES) @@ -2224,13 +2238,13 @@ run; @returns outds contains the following variables: - - directory (containing folder) - - file_or_folder (file / folder) - - filepath (path/to/file.name) - - filename (just the file name) - - ext (.extension) - - msg (system message if any issues) - - OS SPECIFIC variables, if getattrs= is used. + - directory (containing folder) + - file_or_folder (file / folder) + - filepath (path/to/file.name) + - filename (just the file name) + - ext (.extension) + - msg (system message if any issues) + - OS SPECIFIC variables, if getattrs= is used. @version 9.2 @author Allan Bowe @@ -2243,8 +2257,11 @@ run; )/*/STORE SOURCE*/; %let getattrs=%upcase(&getattrs)XX; -data &outds (compress=no 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; +data &outds(compress=no + 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; rc = filename(fref, "&path"); %end; @@ -2253,15 +2270,15 @@ data &outds (compress=no keep=file_or_folder filepath filename ext msg directory rc=0; %end; if rc = 0 then do; - did = dopen(fref); - directory=dinfo(did,'Directory'); - if did=0 then do; - putlog "NOTE: This directory is empty - " directory; - msg=sysmsg(); - put _all_; - stop; - end; - rc = filename(fref); + did = dopen(fref); + directory=dinfo(did,'Directory'); + if did=0 then do; + putlog "NOTE: This directory is empty - " directory; + msg=sysmsg(); + put _all_; + stop; + end; + rc = filename(fref); end; else do; msg=sysmsg(); @@ -2284,7 +2301,7 @@ data &outds (compress=no keep=file_or_folder filepath filename ext msg directory if index(fmsg,'File is in use') or index(dmsg,'is not a directory') then file_or_folder='file'; - else if index(fmsg, 'Insufficient authorization') then file_or_folder='file'; + else if index(fmsg,'Insufficient authorization') then file_or_folder='file'; else if file_or_folder='' then file_or_folder='locked'; if file_or_folder='file' then do; @@ -2371,7 +2388,7 @@ run; **/ %macro mp_distinctfmtvalues( - libds= + libds= ,var= ,outvar=formatted_value ,outds=work.mp_distinctfmtvalues @@ -2386,7 +2403,7 @@ run; create table &outds as select distinct %if &vtype=C & %trim(&fmt)=%str() %then %do; - &var + &var %end; %else %if &vtype=C %then %do; put(&var,&fmt) @@ -2397,7 +2414,7 @@ run; %else %do; put(&var,&fmt) %end; - as &outvar length=&varlen + as &outvar length=&varlen from &libds; %mend;/** @file @@ -2423,7 +2440,7 @@ run; **/ %macro mp_dropmembers( - list /* space separated list of datasets / views */ + list /* space separated list of datasets / views */ ,libref=WORK /* can only drop from a single library at a time */ )/*/STORE SOURCE*/; @@ -2448,20 +2465,22 @@ run; , maxobs=5) TODO: - - labelling the dataset - - explicity setting a unix LF - - constraints / indexes etc + - labelling the dataset + - explicity setting a unix LF + - constraints / indexes etc - @param [in] base_ds= Should be two level - eg work.blah. This is the table that - is converted to a cards file. - @param [in] tgt_ds= Table that the generated cards file would create. Optional - - if omitted, will be same as BASE_DS. + @param [in] base_ds= Should be two level - eg work.blah. This is the table + that is converted to a cards file. + @param [in] tgt_ds= Table that the generated cards file would create. + Optional - if omitted, will be same as BASE_DS. @param [out] cards_file= Location in which to write the (.sas) cards file - @param [in] maxobs= to limit output to the first maxobs observations - @param [in] showlog= whether to show generated cards file in the SAS log (YES/NO) + @param [in] maxobs= to limit output to the first maxobs + observations + @param [in] showlog= whether to show generated cards file in the SAS log + (YES/NO) @param [in] outencoding= provide encoding value for file statement (eg utf-8) - @param [in] append= If NO then will rebuild the cards file if it already exists, - otherwise will append to it. Used by the mp_lib2cards.sas macro. + @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 @@ -2479,8 +2498,8 @@ run; %local i setds nvars; %if not %sysfunc(exist(&base_ds)) %then %do; - %put WARNING: &base_ds does not exist; - %return; + %put WARNING: &base_ds does not exist; + %return; %end; %if %index(&base_ds,.)=0 %then %let base_ds=WORK.&base_ds; @@ -2502,10 +2521,12 @@ select count(*) into: nvars from dictionary.columns %end; /* get indexes */ -proc sort data=sashelp.vindex - (where=(upcase(libname)="%scan(%upcase(&base_ds),1)" - and upcase(memname)="%scan(%upcase(&base_ds),2)")) - out=_data_; +proc sort + data=sashelp.vindex( + where=(upcase(libname)="%scan(%upcase(&base_ds),1)" + and upcase(memname)="%scan(%upcase(&base_ds),2)") + ) + out=_data_; by indxname indxpos; run; @@ -2520,7 +2541,7 @@ data _null_; idxcnt+1; nom=''; uni=''; - vars=name; + vars=name; end; else vars=catx(' ',vars,name); if last.indxname then do; @@ -2548,8 +2569,8 @@ proc sql ; reset outobs=max; create table datalines1 as - select name,type,length,varnum,format,label from dictionary.columns - where libname="%upcase(%scan(&base_ds,1))" + select name,type,length,varnum,format,label from dictionary.columns + where libname="%upcase(%scan(&base_ds,1))" and memname="%upcase(%scan(&base_ds,2))"; /** @@ -2564,7 +2585,7 @@ create table datalines1 as data datalines_2; format dataline $32000.; - set datalines1 (where=(upcase(name) not in + set datalines1 (where=(upcase(name) not in ('PROCESSED_DTTM','VALID_FROM_DTTM','VALID_TO_DTTM'))); if type='num' then dataline= cats('ifc(int(',name,')=',name,' @@ -2578,9 +2599,9 @@ proc sql noprint; select dataline into: datalines separated by ',' from datalines_2; %local - process_dttm_flg - valid_from_dttm_flg - valid_to_dttm_flg + process_dttm_flg + valid_from_dttm_flg + valid_to_dttm_flg ; %let process_dttm_flg = N; %let valid_from_dttm_flg = N; @@ -2650,7 +2671,7 @@ data _null_; put "input "; %do i = 1 %to &nvars.; %if(%length(&&input_stmt_&i..)) %then - put " &&input_stmt_&i.."; + put " &&input_stmt_&i.."; ; %end; put ";"; @@ -2706,8 +2727,8 @@ quit; )/*/STORE SOURCE*/; %if not %sysfunc(exist(&ds)) %then %do; - %put WARNING: &ds does not exist; - %return; + %put WARNING: &ds does not exist; + %return; %end; %if %index(&ds,.)=0 %then %let ds=WORK.&ds; @@ -2723,22 +2744,22 @@ quit; /* first get headers */ data _null_; - file &outloc dlm=',' dsd &outencoding lrecl=32767; - length header $ 2000; - dsid=open("&ds.","i"); - num=attrn(dsid,"nvars"); - do i=1 to num; - header = trim(left(coalescec(varlabel(dsid,i),varname(dsid,i)))); - put header @; - end; - rc=close(dsid); + file &outloc dlm=',' dsd &outencoding lrecl=32767; + length header $ 2000; + dsid=open("&ds.","i"); + num=attrn(dsid,"nvars"); + do i=1 to num; + header = trim(left(coalescec(varlabel(dsid,i),varname(dsid,i)))); + put header @; + end; + rc=close(dsid); run; /* next, export data */ data _null_; - set &ds.; - file &outloc mod dlm=',' dsd &outencoding lrecl=32767; - put (_all_) (+0); + set &ds.; + file &outloc mod dlm=',' dsd &outencoding lrecl=32767; + put (_all_) (+0); run; @@ -2758,7 +2779,7 @@ run; constraint unq unique(tx_from, dd_type), constraint nnn not null(DD_SHORTDESC) ); - + %mp_getconstraints(lib=work,ds=example,outds=work.constraints) @param lib= The target library (default=WORK) @@ -2793,8 +2814,8 @@ create table &outds as on a.TABLE_CATALOG=b.TABLE_CATALOG and a.TABLE_NAME=b.TABLE_NAME and a.constraint_name=b.constraint_name - where a.TABLE_CATALOG="&lib" - and b.TABLE_CATALOG="&lib" + where a.TABLE_CATALOG="&lib" + and b.TABLE_CATALOG="&lib" %if "&ds" ne "" %then %do; and a.TABLE_NAME="&ds" and b.TABLE_NAME="&ds" @@ -2936,7 +2957,9 @@ run; if notnull='yes' then notnul=' not null'; 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 put ' ' name typ '[' notnul ',' lab ']'; @@ -2969,7 +2992,7 @@ run; call symputx('constcheck',1); 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; curds="&curds"; @@ -2979,7 +3002,8 @@ 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); set &idxinfo (where=( libname="%scan(&curds,1,.)" @@ -2990,7 +3014,7 @@ run; file &outref mod; by idxusage indxname; 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 {'; length cols $5000; @@ -3020,8 +3044,8 @@ run; %end; /** - * now we need to figure out the relationships - */ + * now we need to figure out the relationships + */ /* sort alphabetically so we can have one set of unique cols per table */ proc sort data=&pkds nodupkey; @@ -3029,7 +3053,7 @@ proc sort data=&pkds nodupkey; run; data &pkds.1 (keep=curds col) - &pkds.2 (keep=curds cols); + &pkds.2 (keep=curds cols); set &pkds; by curds const; length retconst $39 cols $5000; @@ -3064,7 +3088,11 @@ run; line='Ref: "'!!"&curds" !!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; run; @@ -3085,7 +3113,9 @@ run; create table &pkds.5b as select curds,count(*) as cnt 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 */ group by 1; create table &pkds.6 as @@ -3155,7 +3185,7 @@ run; @param schema= Choose a preferred schema name (default is to use actual schema ,else libref) @param applydttm= for non SAS DDL, choose if columns are created with native - datetime2 format or regular decimal type + datetime2 format or regular decimal type @version 9.3 @author Allan Bowe **/ @@ -3214,7 +3244,9 @@ create table _data_ as %global constraints_used; data _null_; 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; by constraint_type constraint_name; retain constraints_used; @@ -3289,10 +3321,19 @@ run; put ');'; 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_; *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; by idxusage indxname; /* ds=cats(libname,'.',memname); */ @@ -3356,10 +3397,19 @@ run; /* Extra step for data constraints */ %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_; *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; by idxusage indxname; *ds=cats(libname,'.',memname); @@ -3448,15 +3498,24 @@ run; put ');'; 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_; *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; by idxusage indxname; /* ds=cats(libname,'.',memname); */ if first.indxname then do; - put 'CREATE UNIQUE INDEX "' indxname +(-1) '" ' "ON &schema..&curds (" ; + put 'CREATE UNIQUE INDEX "' indxname +(-1) '" ' "ON &schema..&curds("; put ' "' name +(-1) '"' ; end; else put ' ,"' name +(-1) '"'; @@ -3480,17 +3539,18 @@ run; %mend;/** @file mp_getmaxvarlengths.sas @brief Scans a dataset to find the max length of the variable values - @details + @details This macro will scan a base dataset and produce an output dataset with two columns: - NAME Name of the base dataset column - MAXLEN Maximum length of the data contained therein. - Character fields may be allocated very large widths (eg 32000) of which the maximum - value is likely to be much narrower. This macro was designed to enable a HTML - table to be appropriately sized however this could be used as part of a data - audit to ensure we aren't over-sizing our tables in relation to the data therein. + Character fields may be allocated very large widths (eg 32000) of which the + maximum value is likely to be much narrower. This macro was designed to + enable a HTML table to be appropriately sized however this could be used as + 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. Usage: @@ -3512,7 +3572,7 @@ run; %macro mp_getmaxvarlengths( libds /* libref.dataset to analyse */ - ,outds=work.mp_getmaxvarlengths /* name of output dataset to create */ + ,outds=work.mp_getmaxvarlengths /* name of output dataset to create */ )/*/STORE SOURCE*/; %local vars x var fmt; @@ -3857,8 +3917,8 @@ create table &outds (rename=( @details PROC JSON is faster but will produce errs like the ones below if special chars are encountered. - >An object or array close is not valid at this point in the JSON text. - >Date value out of range + >An object or array close is not valid at this point in the JSON text. + >Date value out of range If this happens, try running with ENGINE=DATASTEP. @@ -3866,7 +3926,7 @@ create table &outds (rename=( filename tmp temp; data class; set sashelp.class;run; - + %mp_jsonout(OBJ,class,jref=tmp) data _null_; @@ -3875,7 +3935,7 @@ create table &outds (rename=( run; If you are building web apps with SAS then you are strongly encouraged to use - the mX_createwebservice macros in combination with the + the mX_createwebservice macros in combination with the [sasjs adapter](https://github.com/sasjs/adapter). For more information see https://sasjs.io @@ -3891,11 +3951,11 @@ create table &outds (rename=( @param fmt= Whether to keep or strip formats from the table @param engine= Which engine to use to send the JSON, options are: * PROCJSON (default) - * DATASTEP + * DATASTEP @param dbg= DEPRECATED - was used to conditionally add PRETTY to proc json but this can cause line truncation in large files. - + @version 9.2 @author Allan Bowe @@ -3919,7 +3979,7 @@ create table &outds (rename=( %if &engine=PROCJSON %then %do; data;run;%let tempds=&syslast; proc sql;drop table &tempds; - data &tempds /view=&tempds;set &ds; + data &tempds /view=&tempds;set &ds; %if &fmt=N %then format _numeric_ best32.;; proc json out=&jref pretty %if &action=ARR %then nokeys ; @@ -3934,13 +3994,14 @@ create table &outds (rename=( %put &sysmacroname: &ds NOT FOUND!!!; %return; %end; - data _null_;file &jref mod ; + data _null_;file &jref mod ; 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_; by varnum; - data _null_; + data _null_; set _last_ end=last; call symputx(cats('name',_n_),name,'l'); call symputx(cats('type',_n_),type,'l'); @@ -3974,8 +4035,9 @@ create table &outds (rename=( )))))!!'"'; %end; %end; - run; - /* write to temp loc to avoid _webout truncation - https://support.sas.com/kb/49/325.html */ + run; + /* write to temp loc to avoid _webout truncation + - https://support.sas.com/kb/49/325.html */ filename _sjs temp lrecl=131068 encoding='utf-8'; data _null_; file _sjs lrecl=131068 encoding='utf-8' mod; set &tempds; @@ -3984,7 +4046,7 @@ create table &outds (rename=( %do i=1 %to &cols; %if &i>1 %then "," ; %if &action=OBJ %then """&&name&i"":" ; - &&name&i + &&name&i %end; %if &action=ARR %then "]" ; %else "}" ; ; proc sql; @@ -4248,13 +4310,13 @@ run; %mp_prevobs(INIT,history=2) if _n_ =10 then do; %* fetch previous but 1 record; - %mp_prevobs(FETCH,-2) - put _n_= name= age= calc_var=; + %mp_prevobs(FETCH,-2) + put _n_= name= age= calc_var=; %* fetch previous record; - %mp_prevobs(FETCH,-1) - put _n_= name= age= calc_var=; + %mp_prevobs(FETCH,-1) + put _n_= name= age= calc_var=; %* reinstate current record ; - %mp_prevobs(FETCH,0) + %mp_prevobs(FETCH,0) put _n_= name= age= calc_var=; end; run; @@ -4267,11 +4329,11 @@ run; https://www.lexjansen.com/pharmasug/2008/cc/CC08.pdf @param action Either FETCH a current or previous record, or INITialise. - @param record The relative (to current) position of the previous observation - to return. + @param record The relative (to current) position of the previous observation + to return. @param history= The number of records to retain in the hash table. Default=5 @param prefix= the prefix to give to the variables used to store the hash name - and index. Default=mp_prevobs + and index. Default=mp_prevobs @version 9.2 @author Allan Bowe @@ -4285,33 +4347,33 @@ run; %let record=%eval((&record+0) * -1); %if &action=INIT %then %do; - - if _n_ eq 1 then do; - attrib &prefix._VAR length=$64; + + if _n_ eq 1 then do; + attrib &prefix._VAR length=$64; dcl hash &prefix._HASH(ordered:'Y'); &prefix._KEY=0; - &prefix._HASH.defineKey("&prefix._KEY"); - do while(1); - call vnext(&prefix._VAR); + &prefix._HASH.defineKey("&prefix._KEY"); + do while(1); + call vnext(&prefix._VAR); if &prefix._VAR='' then leave; - if &prefix._VAR eq "&prefix._VAR" then continue; - else if &prefix._VAR eq "&prefix._KEY" then continue; + if &prefix._VAR eq "&prefix._VAR" then continue; + else if &prefix._VAR eq "&prefix._KEY" then continue; &prefix._HASH.defineData(&prefix._VAR); - end; - &prefix._HASH.defineDone(); + end; + &prefix._HASH.defineDone(); end; /* this part has to happen before FETCHing */ &prefix._KEY+1; &prefix._rc=&prefix._HASH.add(); if &prefix._rc then putlog 'adding' &prefix._rc=; %if &history>0 %then %do; - if &prefix._key>&history+1 then + if &prefix._key>&history+1 then &prefix._HASH.remove(key: &prefix._KEY - &history - 1); if &prefix._rc then putlog 'removing' &prefix._rc=; %end; %end; %else %if &action=FETCH %then %do; - if &record > &prefix._key then putlog "Not enough records in &Prefix._hash yet"; + if &record>&prefix._key then putlog "Not enough records in &Prefix._hash yet"; else &prefix._rc=&prefix._HASH.find(key: &prefix._KEY - &record); if &prefix._rc then putlog &prefix._rc= " when fetching " &prefix._KEY= "with record &record and " _n_=; @@ -4351,9 +4413,9 @@ run; @returns outds contains the following variables: - - level (0 = top level) - - &parentvar - - &childvar (null if none found) + - level (0 = top level) + - &parentvar + - &childvar (null if none found) @version 9.2 @author Allan Bowe @@ -4448,14 +4510,14 @@ run; rootlib |-- LIBREF1 - | |__ mytable.ddl - | |__ someothertable.ddl + | |__ mytable.ddl + | |__ someothertable.ddl |-- LIBREF2 - | |__ table1.ddl - | |__ table2.ddl + | |__ table1.ddl + | |__ table2.ddl |-- LIBREF3 - |__ table3.ddl - |__ table4.ddl + |__ table3.ddl + |__ table4.ddl Only files with the .ddl suffix are executed. The parent folder name is used as the libref. @@ -4520,7 +4582,7 @@ create table _data_ as where upcase(libname) in ("IMPOSSIBLE", %local x; %do x=1 %to %sysfunc(countw(&libs)); - "%upcase(%scan(&libs,&x))" + "%upcase(%scan(&libs,&x))" %end; ) %end; @@ -4562,7 +4624,7 @@ proc sort; by descending sumcols memname libname; run; @brief Searches all data in a library @details Scans an entire library and creates a copy of any table - containing a specific string OR numeric value. Only + containing a specific string OR numeric value. Only matching records are written out. If both a string and numval are provided, the string will take precedence. @@ -4579,9 +4641,10 @@ proc sort; by descending sumcols memname libname; run; @param ds= the dataset to search (leave blank to search entire library) @param string= the string value to search @param numval= the numeric value to search (must be exact) - @param outloc= the directory in which to create the output datasets with 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 outloc= the directory in which to create the output datasets with + 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 filter_text= add a (valid) filter clause to further filter the results

SAS Macros

@@ -4595,7 +4658,7 @@ proc sort; by descending sumcols memname libname; run; **/ %macro mp_searchdata(lib=sashelp - ,ds= + ,ds= ,string= /* the query will use a contains (?) operator */ ,numval= /* numeric must match exactly */ ,outloc=%sysfunc(pathname(work))/mpsearch @@ -4603,7 +4666,8 @@ proc sort; by descending sumcols memname libname; run; ,filter_text=%str(1=1) )/*/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.); %if &syscc ge 4 %then %do; @@ -4620,14 +4684,14 @@ libname mpsearch "&outloc"; /* get the list of tables in the library */ proc sql noprint; select distinct memname into: table_list separated by ' ' - from dictionary.tables + from dictionary.tables where upcase(libname)="%upcase(&lib)" %if &ds ne %then %do; and upcase(memname)=%upcase("&ds") %end; ; /* check that we have something to check */ -proc sql +proc sql %if &outobs>-1 %then %do; outobs=&outobs %end; @@ -4644,7 +4708,7 @@ proc sql %let check_tm=%sysfunc(datetime()); /* build sql statement */ create table mpsearch.&table as select * from &lib..&table - where %unquote(&filter_text) and + where %unquote(&filter_text) and (0 /* loop through columns */ %do colnum=1 %to %sysfunc(countw(&vars,%str( ))); @@ -4660,7 +4724,8 @@ proc sql %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; %put %str(WAR)NING: SQLRC=&sqlrc when processing &table; %return; @@ -4688,7 +4753,7 @@ proc sql @param key Provide a key on which to perform the lookup @param value Provide a value @param type= either C or N will populate valc and valn respectively. C is - default. + default. @param libds= define the target table to hold the parameters @version 9.2 @@ -4728,12 +4793,13 @@ proc sql %mend;/** @file @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): proc sql; create table &libds( - request_dttm num not null format=datetime. + request_dttm num not null format=datetime. ,status_cd char(4) not null ,_metaperson varchar(100) not null ,_program varchar(500) @@ -4805,7 +4871,8 @@ proc sql 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; %mp_streamfile(contenttype=csv,inloc=/some/where.txt,outname=myfile.txt) @@ -4864,13 +4931,15 @@ proc sql %else %if &contentype=XLSX %then %do; %if &platform=SASMETA %then %do; 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"); run; %end; %else %if &platform=SASVIYA %then %do; 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"; %end; %end; @@ -4912,10 +4981,10 @@ proc sql %end; %if &inref ne 0 %then %do; - %mp_binarycopy(inref=&inref,outref=_webout) + %mp_binarycopy(inref=&inref,outref=_webout) %end; %else %do; - %mp_binarycopy(inloc="&inloc",outref=_webout) + %mp_binarycopy(inloc="&inloc",outref=_webout) %end; %mend;/** @@ -4925,7 +4994,7 @@ proc sql testing of arbitrary jobs. %mp_testjob( - duration=60*5 + duration=60*5 ) @param [in] duration= the time in seconds which the job should run for. Actual @@ -5012,10 +5081,10 @@ libname &lib clear; %mend;/** @file mp_testwritespeedlibrary.sas @brief Tests the write speed of a new table in a SAS library - @details Will create a new table of a certain size in an + @details Will create a new table of a certain size in an existing SAS library. The table will have one column, and will be subsequently deleted. - + %mp_testwritespeedlibrary( lib=work ,size=0.5 @@ -5077,8 +5146,11 @@ run; Credits: - * Roger Deangelis, 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 + Roger Deangelis: +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) @@ -5086,13 +5158,13 @@ run; @returns outds contains the following variables: - - `dir`: a flag (1/0) to say whether it is a directory or not. This is not - reliable - folders that you do not have permission to open will be flagged - as directories. - - `ext`: file extension - - `filename`: file name - - `dirname`: directory name - - `fullpath`: directory + file name + - `dir`: a flag (1/0) to say whether it is a directory or not. This is not + reliable - folders that you do not have permission to open will be flagged + as directories. + - `ext`: file extension + - `filename`: file name + - `dirname`: directory name + - `fullpath`: directory + file name @version 9.2 **/ @@ -5502,7 +5574,7 @@ filename __us2grp clear; **/ %macro mm_assigndirectlib( - libref /* libref to assign from metadata */ + libref /* libref to assign from metadata */ ,open_passthrough= /* provide an alias to produce the CONNECT TO statement for the relevant external database */ @@ -5576,7 +5648,7 @@ run; run; %if %sysevalf(&sysver<9.4) %then %do; - libname &libref &filepath; + libname &libref &filepath; %end; %else %do; /* apply the new filelocks option to cater for temporary locks */ @@ -5586,7 +5658,8 @@ run; %end; %else %if &engine=REMOTE %then %do; 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; rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon); @@ -5598,8 +5671,9 @@ run; rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue); rc = metadata_getattr(uriProp , "PropertyName",PropertyName); rc = metadata_getattr(uriProp , "Delimiter",Delimiter); - properties = trim(properties) !! " " !! trim(PropertyName) !! trim(Delimiter) !! trim(PropertyValue); - output; + properties = trim(properties) !! " " !! trim(PropertyName) + !! trim(Delimiter) !! trim(PropertyValue); + output; k+1; rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp); end; @@ -5634,13 +5708,14 @@ run; rc=metadata_getnasn(connx_uri,'Properties',i,conprop_uri); rc2=metadata_getattr(conprop_uri,'Name',value); if value='Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt' then do; - rc3=metadata_getattr(conprop_uri,'DefaultValue',datasource); + rc3=metadata_getattr(conprop_uri,'DefaultValue',datasource); end; 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; - else if value='Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt' then do; - rc5=metadata_getattr(conprop_uri,'DefaultValue',properties); + else if value='Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt' then + do; + rc5=metadata_getattr(conprop_uri,'DefaultValue',properties); end; end; &mD.putlog 'NOTE- dsn/provider/properties: ' / @@ -5663,8 +5738,8 @@ run; /* need additional properties to make this work */ properties=('Integrated Security'=SSPI 'Persist Security Info'=True - %sysfunc(compress(%str(&SQL_properties),%str(()))) - ) + %sysfunc(compress(%str(&SQL_properties),%str(()))) + ) DATASOURCE=&sql_dsn PROMPT=NO PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL); %end; @@ -5672,9 +5747,9 @@ run; LIBNAME &libref OLEDB PROPERTIES=&sql_properties DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema %if %length(&sql_domain)>0 %then %do; - authdomain="&sql_domain" + authdomain="&sql_domain" %end; - connection=shared; + connection=shared; %end; %end; %else %if &engine=ODBC %then %do; @@ -5691,8 +5766,8 @@ run; rc2=metadata_getnasn(connx_uri,'Properties',i,conprop_uri); rc3=metadata_getattr(conprop_uri,'Name',value); if value='Connection.ODBC.Property.DATASRC.Name.xmlKey.txt' then do; - rc4=metadata_getattr(conprop_uri,'DefaultValue',datasource); - rc2=-1; + rc4=metadata_getattr(conprop_uri,'DefaultValue',datasource); + rc2=-1; end; end; /* get SCHEMA */ @@ -5749,7 +5824,7 @@ run; /* get PRESERVE_TAB_NAMES value */ /* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will - become case sensitive!! */ + become case sensitive!! */ prop='Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'; rc=metadata_getprop("&liburi",prop,preserve_tab_names,""); if preserve_tab_names^='' then preserve_tab_names= @@ -5826,7 +5901,8 @@ run; call symputx('authdomain',authdomain,'l'); /* 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'); /* schema */ @@ -5835,27 +5911,30 @@ run; call symputx('schema',schema,'l'); run; %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-; libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain; %end; %else %if &engine=SQLSVR %then %do; %put NOTE: Obtaining &engine library details; 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_); - + rc=metadata_getnasn("&liburi",'DefaultLogin',1,assocuri1); rc=metadata_getattr(assocuri1,"UserID",userid); rc=metadata_getattr(assocuri1,"Password",passwd); call symputx('user',userid,'l'); call symputx('pass',passwd,'l'); - + /* path */ 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'); - + /* schema */ rc=metadata_getnasn("&liburi",'UsingPackages',1,assocuri3); rc=metadata_getattr(assocuri3,'SchemaName',schema); @@ -5863,17 +5942,19 @@ run; run; %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-; - libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass" ; + libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass"; %end; %else %if &engine=TERADATA %then %do; %put NOTE: Obtaining &engine library details; 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_); - + /* get auth domain */ rc=metadata_getnasn("&liburi",'LibraryConnection',1,assocuri1); rc=metadata_getnasn(assocuri1,'Domain',1,assocuri2); @@ -5890,9 +5971,10 @@ run; /* path */ 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'); - + /* schema */ rc=metadata_getnasn("&liburi",'UsingPackages',1,assocuri3); rc=metadata_getattr(assocuri3,'SchemaName',schema); @@ -5900,7 +5982,8 @@ run; run; %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-; libname &libref TERADATA server=&path schema=&schema authdomain=&authdomain; @@ -5935,7 +6018,8 @@ run; @li mp_abort.sas @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 @@ -5945,7 +6029,7 @@ run; **/ %macro mm_assignlib( - libref + libref ,mAbort=HARD )/*/STORE SOURCE*/; @@ -6011,7 +6095,9 @@ run; ,params= name1=value1 name2=value2 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).

SAS Macros

@li mp_abort.sas @@ -6058,8 +6144,8 @@ run; %mf_verifymacvars(tree name) /** - * check tree exists - */ + * check tree exists + */ data _null_; length type uri $256; @@ -6075,8 +6161,8 @@ run; ) /** - * Check object does not exist already - */ + * Check object does not exist already + */ data _null_; length type uri $256; rc=metadata_pathobj("","&tree/&name","Application",type,uri); @@ -6092,8 +6178,8 @@ run; /** - * Now we can create the application - */ + * Now we can create the application + */ filename &frefin temp; /* write header XML */ @@ -6282,8 +6368,8 @@ run; %mf_verifymacvars(tree name) /** - * check tree exists - */ + * check tree exists + */ data _null_; length type uri $256; @@ -6299,8 +6385,8 @@ run; ) /** - * Check object does not exist already - */ + * Check object does not exist already + */ data _null_; length type uri $256; rc=metadata_pathobj("","&tree/&name","Note",type,uri); @@ -6315,8 +6401,8 @@ run; %end; /** - * Now we can create the document - */ + * Now we can create the document + */ filename &frefin temp; /* write header XML */ @@ -6411,7 +6497,7 @@ data _null_; * must have a starting slash ; 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; end; @@ -6431,8 +6517,8 @@ data _null_; * check that root folder exists ; root=cats('/',scan(folderpath,1,'/'),"(Folder)"); if metadata_pathobj('',root,"",objType,parentId)<1 then do; - put "%str(ERR)OR: " root " does not exist!"; - stop; + put "%str(ERR)OR: " root " does not exist!"; + stop; end; * check that parent folder exists ; @@ -6442,21 +6528,21 @@ data _null_; if rc<1 then do; putlog 'The following folders will be created:'; /* folder does not exist - so start from top and work down */ - length newpath $1000; - paths=0; - do x=2 to countw(folderpath,'/'); - newpath=''; - do i=1 to x; - newpath=cats(newpath,'/',scan(folderpath,i,'/')); - end; - rc=metadata_pathobj('',cats(newpath,"(Folder)"),"",objType,parentId); - if rc<1 then do; - paths+1; - call symputx(cats('path',paths),newpath); - putlog newpath; - end; - call symputx('paths',paths); - end; + length newpath $1000; + paths=0; + do x=2 to countw(folderpath,'/'); + newpath=''; + do i=1 to x; + newpath=cats(newpath,'/',scan(folderpath,i,'/')); + end; + rc=metadata_pathobj('',cats(newpath,"(Folder)"),"",objType,parentId); + if rc<1 then do; + paths+1; + call symputx(cats('path',paths),newpath); + putlog newpath; + end; + call symputx('paths',paths); + end; end; else putlog "parent " parent " exists"; @@ -6471,7 +6557,7 @@ run; %if &paths>0 %then %do x=1 %to &paths; %put executing recursive call for &&path&x; - %mm_createfolder(path=&&path&x) + %mm_createfolder(path=&&path&x) %end; %else %do; filename __newdir temp; @@ -6479,9 +6565,10 @@ run; %local inmeta; %put creating: &path; %let inmeta=$METAREPOSITORY - - - SAS268435456; + + SAS268435456 + ; proc metadata in="&inmeta" out=__newdir verbose; run ; @@ -6570,7 +6657,7 @@ run; **/ %macro mm_createlibrary( - libname=My New Library + libname=My New Library ,libref=mynewlib ,libdesc=Created automatically using the mm_createlibrary macro ,engine=BASE @@ -6592,8 +6679,8 @@ run; %let libref=%upcase(&libref); /** - * Check Library does not exist already with this libname - */ + * Check Library does not exist already with this libname + */ data _null_; length type uri $256; rc=metadata_resolve("omsobj:SASLibrary?@Name='&libname'",type,uri); @@ -6607,8 +6694,8 @@ run; %end; /** - * Check Library does not exist already with this libref - */ + * Check Library does not exist already with this libref + */ data _null_; length type uri $256; rc=metadata_resolve("omsobj:SASLibrary?@Libref='&libref'",type,uri); @@ -6623,13 +6710,13 @@ run; /** - * Attempt to create tree - */ + * Attempt to create tree + */ %mm_createfolder(path=&tree) /** - * check tree exists - */ + * check tree exists + */ data _null_; length type uri $256; rc=metadata_pathobj("","&tree","Folder",type,uri); @@ -6642,8 +6729,8 @@ run; %end; /** - * Create filerefs for proc metadata call - */ + * Create filerefs for proc metadata call + */ filename &frefin temp; filename &frefout temp; @@ -6654,8 +6741,8 @@ filename &frefout temp; /** - * Check that the ServerContext exists - */ + * Check that the ServerContext exists + */ data _null_; length type uri $256; rc=metadata_resolve("omsobj:ServerContext?@Name='&ServerContext'",type,uri); @@ -6669,8 +6756,8 @@ filename &frefout temp; %end; /** - * Get prototype info - */ + * Get prototype info + */ data _null_; length type uri str $256; str="omsobj:Prototype?@Name='Library.SAS.Prototype.Name.xmlKey.txt'"; @@ -6680,21 +6767,21 @@ filename &frefout temp; putlog (_all_)(=); run; %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; %end; /** - * Check that Physical location exists - */ + * Check that Physical location exists + */ %if %sysfunc(fileexist(&directory))=0 %then %do; %put %str(ERR)OR: Physical directory (&directory) does not appear to exist!; %return; %end; /** - * Check that Directory Object exists in metadata - */ + * Check that Directory Object exists in metadata + */ data _null_; length type uri $256; rc=metadata_resolve("omsobj:Directory?@DirectoryRole='LibraryPath'" @@ -6742,16 +6829,16 @@ filename &frefout temp; %end; /** - * check SAS version - */ + * check SAS version + */ %if %sysevalf(&sysver lt 9.3) %then %do; %put WARNING: Version 9.3 or later required; %return; %end; /** - * Prepare the XML and create the library - */ + * Prepare the XML and create the library + */ data _null_; file &frefin; treeuri=quote(symget('treeuri')); @@ -6825,8 +6912,8 @@ filename &frefout temp; /** - * Wrap up - */ + * Wrap up + */ %if &mdebug ne 1 %then %do; filename &frefin clear; filename &frefout clear; @@ -6907,10 +6994,10 @@ filename &frefout temp; foundation repo then select a different one here @returns outds dataset containing the following columns: - - stpuri - - prompturi - - fileuri - - texturi + - stpuri + - prompturi + - fileuri + - texturi @version 9.2 @author Allan Bowe @@ -6918,7 +7005,7 @@ filename &frefout temp; **/ %macro mm_createstp( - stpname=Macro People STP + stpname=Macro People STP ,stpdesc=This stp was created automatically by the mm_createstp macro ,filename=mm_createstp.sas ,directory=SASEnvironment/SASCode @@ -6944,8 +7031,8 @@ filename &frefout temp; %mp_dropmembers(%scan(&outds,2,.)) /** - * check tree exists - */ + * check tree exists + */ data _null_; length type uri $256; rc=metadata_pathobj("","&tree","Folder",type,uri); @@ -6958,8 +7045,8 @@ run; %end; /** - * Check STP does not exist already - */ + * Check STP does not exist already + */ %local cmtype; data _null_; length type uri $256; @@ -6973,8 +7060,8 @@ run; %end; /** - * Check that the physical file exists - */ + * Check that the physical file exists + */ %if %sysfunc(fileexist(&directory/&filename)) ne 1 %then %do; %put WARNING: FILE *&directory/&filename* NOT FOUND!; %return; @@ -7045,7 +7132,8 @@ run; rc3=METADATA_SETATTR(prompturi, 'GroupType','2'); rc4=METADATA_SETATTR(prompturi, 'Name','Parameters'); rc5=METADATA_SETATTR(prompturi, 'PublicType','Embedded:PromptGroup'); - GroupInfo=""; rc6 = METADATA_SETATTR(prompturi, 'GroupInfo',groupinfo); @@ -7150,8 +7238,8 @@ run; %end; /** - * First, create a Hello World type 2 stored process - */ + * First, create a Hello World type 2 stored process + */ filename &frefin temp; data _null_; file &frefin; @@ -7206,8 +7294,8 @@ run; %end; /** - * Next, add the source code - */ + * Next, add the source code + */ %mm_updatestpsourcecode(stp=&tree/&stpname ,stpcode="&directory/&filename" ,frefin=&frefin. @@ -7245,7 +7333,7 @@ Usage: %webout(OBJ,example2) * Object format, easier to work with ; %webout(CLOSE) ;;;; - %mm_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001,replace=YES) + %mm_createwebservice(path=/Public/app/common,name=appInit)

SAS Macros

@li mm_createstp.sas @@ -7299,10 +7387,10 @@ Usage: %let path=%substr(&path,1,%length(&path)-1); /** - * Add webout macro - * These put statements are auto generated - to change the macro, change the - * source (mm_webout) and run `build.py` - */ + * Add webout macro + * These put statements are auto generated - to change the macro, change the + * source (mm_webout) and run `build.py` + */ filename sasjs temp; data _null_; file sasjs lrecl=3000 ; @@ -7342,7 +7430,8 @@ data _null_; put ' %end; '; put ' data _null_;file &jref mod ; '; 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 ' by varnum; '; put ' '; @@ -7381,7 +7470,8 @@ data _null_; put ' %end; '; put ' %end; '; 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 ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; '; put ' set &tempds; '; @@ -7648,12 +7738,12 @@ run; **/ %macro mm_deletedocument( - target= + target= )/*/STORE SOURCE*/; /** - * Check document exist - */ + * Check document exist + */ %local type; data _null_; length type uri $256; @@ -7669,10 +7759,10 @@ run; filename __in temp lrecl=10000; filename __out temp lrecl=10000; data _null_ ; - file __in ; - put ""; - put "SAS268436480"; - put ""; + file __in ; + put ""; + put "SAS268436480"; + put ""; run ; proc metadata in=__in out=__out verbose;run; @@ -7683,8 +7773,8 @@ filename __in clear; filename __out clear; /** - * Check deletion - */ + * Check deletion + */ %local isgone; data _null_; length type uri $256; @@ -7810,12 +7900,12 @@ run; **/ %macro mm_deletestp( - target= + target= )/*/STORE SOURCE*/; /** - * Check STP does exist - */ + * Check STP does exist + */ %local cmtype; data _null_; length type uri $256; @@ -7831,10 +7921,10 @@ run; filename __in temp lrecl=10000; filename __out temp lrecl=10000; data _null_ ; - file __in ; - put ""; - put "SAS268436480"; - put ""; + file __in ; + put ""; + put "SAS268436480"; + put ""; run ; proc metadata in=__in out=__out verbose;run; @@ -7845,8 +7935,8 @@ filename __in clear; filename __out clear; /** - * Check deletion - */ + * Check deletion + */ %local isgone; data _null_; length type uri $256; @@ -7884,8 +7974,8 @@ run; )/*/STORE SOURCE*/; %if %length(&outds)>30 %then %do; - %put %str(ERR)OR: Temp tables are created with the &outds prefix, which therefore - needs to be 30 characters or less; + %put %str(ERR)OR: Temp tables are created with the &outds prefix, which + therefore needs to be 30 characters or less; %return; %end; %if %index(&outds,'.')>0 %then %do; @@ -7921,11 +8011,11 @@ data _null_; put str; if last then do; /* collate attributes */ - str=cats("data &outds._logat; set &outds.da1-&outds.da",_n_,";run;"); - put str; + str=cats("data &outds._logat; set &outds.da1-&outds.da",_n_,";run;"); + put str; /* collate associations */ - str=cats("data &outds._logas; set &outds.a1-&outds.a",_n_,";run;"); - put str; + str=cats("data &outds._logas; set &outds.a1-&outds.a",_n_,";run;"); + put str; /* tidy up */ str=cats("proc delete data=&outds.da1-&outds.da",_n_,";run;"); put str; @@ -7999,7 +8089,7 @@ filename &fileref clear; **/ %macro mm_getcols( - tableuri= + tableuri= ,outds=work.mm_getcols )/*/STORE SOURCE*/; @@ -8104,9 +8194,9 @@ run; @param mDebug= set to 1 to show debug messages in the log @returns outds dataset containing the following columns: - - directoryuri - - groupname - - groupdesc + - directoryuri + - groupname + - groupdesc @version 9.2 @author Allan Bowe @@ -8114,7 +8204,7 @@ run; **/ %macro mm_getDirectories( - path= + path= ,outds=work.mm_getDirectories ,mDebug=0 )/*/STORE SOURCE*/; @@ -8133,8 +8223,10 @@ data &outds (keep=directoryuri name directoryname directorydesc ); do while (metadata_getnobj("omsobj:Directory?@Id contains '.'",__i,directoryuri)>0); %end; %else %do; - do while - (metadata_getnobj("omsobj:Directory?@DirectoryName='&path'",__i,directoryuri)>0); + do while( + metadata_getnobj("omsobj:Directory?@DirectoryName='&path'",__i,directoryuri) + >0 + ); %end; __rc1=metadata_getattr(directoryuri, "Name", name); __rc2=metadata_getattr(directoryuri, "DirectoryName", directoryname); @@ -8186,8 +8278,8 @@ run; %&mD.put _local_; /** - * check tree exists - */ + * check tree exists + */ data _null_; length type uri $256; @@ -8203,8 +8295,8 @@ run; ) /** - * Check object exists - */ + * Check object exists + */ data _null_; length type docuri tsuri tsid $256 ; rc1=metadata_pathobj("","&tree/&name","Note",type,docuri); @@ -8222,14 +8314,14 @@ run; ) /** - * Now we can extract the textstore - */ + * Now we can extract the textstore + */ filename __getdoc temp lrecl=10000000; proc metadata - in="$METAREPOSITORY - - SAS1" - out=__getdoc ; + in="$METAREPOSITORY + + SAS1" + out=__getdoc ; run; /* find the beginning of the text */ @@ -8247,47 +8339,47 @@ data _null_; /* read the content, byte by byte, resolving escaped chars */ filename __outdoc "&outref" lrecl=100000; data _null_; - length filein 8 fileid 8; - filein = fopen("__getdoc","I",1,"B"); - fileid = fopen("__outdoc","O",1,"B"); - rec = "20"x; - length entity $6; - do while(fread(filein)=0); - x+1; - if x>&start then do; - rc = fget(filein,rec,1); - if rec='"' then leave; - else if rec="&" then do; - entity=rec; - do until (rec=";"); - if fread(filein) ne 0 then goto getout; - rc = fget(filein,rec,1); - entity=cats(entity,rec); + length filein 8 fileid 8; + filein = fopen("__getdoc","I",1,"B"); + fileid = fopen("__outdoc","O",1,"B"); + rec = "20"x; + length entity $6; + do while(fread(filein)=0); + x+1; + if x>&start then do; + rc = fget(filein,rec,1); + if rec='"' then leave; + else if rec="&" then do; + entity=rec; + do until (rec=";"); + if fread(filein) ne 0 then goto getout; + rc = fget(filein,rec,1); + entity=cats(entity,rec); + end; + select (entity); + when ('&' ) rec='&' ; + when ('<' ) rec='<' ; + when ('>' ) rec='>' ; + when (''') rec="'" ; + when ('"') rec='"' ; + when (' ') rec='0A'x; + when (' ') rec='0D'x; + when ('$' ) rec='$' ; + when (' ') rec='09'x; + otherwise putlog "WARNING: missing value for " entity=; + end; + rc =fput(fileid, substr(rec,1,1)); + rc =fwrite(fileid); end; - select (entity); - when ('&' ) rec='&' ; - when ('<' ) rec='<' ; - when ('>' ) rec='>' ; - when (''') rec="'" ; - when ('"') rec='"' ; - when (' ') rec='0A'x; - when (' ') rec='0D'x; - when ('$' ) rec='$' ; - when (' ') rec='09'x; - otherwise putlog "WARNING: missing value for " entity=; + else do; + rc =fput(fileid,rec); + rc =fwrite(fileid); end; - rc =fput(fileid, substr(rec,1,1)); - rc =fwrite(fileid); end; - else do; - rc =fput(fileid,rec); - rc =fwrite(fileid); - end; - end; - end; - getout: - rc=fclose(filein); - rc=fclose(fileid); + end; + getout: + rc=fclose(filein); + rc=fclose(fileid); run; filename __getdoc clear; filename __outdoc clear; @@ -8306,7 +8398,8 @@ filename __outdoc clear; %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 [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

Data Outputs

@@ -8315,11 +8408,11 @@ filename __outdoc clear; |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.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|

SAS Macros

@@ -8332,7 +8425,7 @@ filename __outdoc clear; **/ %macro mm_getfoldermembers( - root= + root= ,outds=work.mm_getfoldertree )/*/STORE SOURCE*/; @@ -8403,7 +8496,8 @@ filename __outdoc clear; options notes source; @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 [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

SAS Macros

@@ -8413,7 +8507,7 @@ filename __outdoc clear; **/ %macro mm_getfoldertree( - root= + root= ,outds=work.mm_getfoldertree ,mDebug=0 ,depth=50 /* how many nested folders to query */ @@ -8482,11 +8576,11 @@ run; @file @brief Creates dataset with all members of a metadata group @details - + usage: - + %mm_getgroupmembers(someGroupName - ,outds=work.mm_getgroupmembers + ,outds=work.mm_getgroupmembers ,emails=YES) @param group metadata group for which to bring back members @@ -8563,14 +8657,15 @@ run; @param [in] user= the metadata user to return groups for. Leave blank for all 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 [out] outds= the dataset to create that contains the list of groups @returns outds dataset containing all groups in a column named "metagroup" - - groupuri - - groupname - - groupdesc + - groupuri + - groupname + - groupdesc @version 9.2 @author Allan Bowe @@ -8578,7 +8673,7 @@ run; **/ %macro mm_getGroups( - user= + user= ,outds=work.mm_getGroups ,repo=foundation ,mDebug=0 @@ -8591,7 +8686,8 @@ run; %&mD.put Executing mm_getGroups.sas; %&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; options metarepository=&repo; %end; @@ -8715,6 +8811,14 @@ run; |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. @@ -8811,7 +8915,7 @@ run; filename response temp; /* get list of libraries */ proc metadata in= - ' + ' $METAREPOSITORY SASLibrary @@ -8889,10 +8993,10 @@ libname _XML_ clear; filename response temp; /* get list of libraries */ proc metadata in= - "$METAREPOSITORY - &typeSAS - 0" - out=response; + "$METAREPOSITORY + &typeSAS + 0" + out=response; run; /* write the response to the log for debugging */ @@ -8907,7 +9011,8 @@ filename sxlemap temp; data _null_; file sxlemap; put ''; - put "/GetMetadataObjects/Objects/&type"; + put "/GetMetadataObjects/Objects/&type"; + put ""; put ''; put "/GetMetadataObjects/Objects/&type/@Id"; put "characterstring200"; @@ -8930,8 +9035,9 @@ libname _XML_ clear; %mend;/** @file mm_getpublictypes.sas @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: * dataset will contain one column - publictype ($64); @@ -9039,8 +9145,8 @@ quit; filename response temp; /* get list of libraries */ proc metadata in= - "1" - out=response; + "1" + out=response; run; /* write the response to the log for debugging */ @@ -9057,61 +9163,76 @@ filename sxlemap temp; data _null_; file sxlemap; put '
'; - put "/GetRepositories/Repositories/Repository"; + put "/GetRepositories/Repositories/Repository"; + put ""; put ''; - put "/GetRepositories/Repositories/Repository/@Id"; + put "/GetRepositories/Repositories/Repository/@Id"; + put ""; put "characterstring200"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@Name"; + put "/GetRepositories/Repositories/Repository/@Name"; + put ""; put "characterstring200"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@Desc"; + put "/GetRepositories/Repositories/Repository/@Desc"; + put ""; put "characterstring200"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@DefaultNS"; + put ""; + put "/GetRepositories/Repositories/Repository/@DefaultNS"; put "characterstring200"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@RepositoryType"; + put ""; + put "/GetRepositories/Repositories/Repository/@RepositoryType"; put "characterstring20"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@RepositoryFormat"; + put ""; + put "/GetRepositories/Repositories/Repository/@RepositoryFormat"; put "characterstring10"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@Access"; + put ""; + put "/GetRepositories/Repositories/Repository/@Access"; put "characterstring16"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@CurrentAccess"; + put ""; + put "/GetRepositories/Repositories/Repository/@CurrentAccess"; put "characterstring16"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@PauseState"; + put ""; + put "/GetRepositories/Repositories/Repository/@PauseState"; put "characterstring16"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@Path"; + put "/GetRepositories/Repositories/Repository/@Path"; + put ""; put "characterstring256"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@Engine"; + put "/GetRepositories/Repositories/Repository/@Engine"; + put ""; put "characterstring8"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@Options"; + put "/GetRepositories/Repositories/Repository/@Options"; + put ""; put "characterstring32"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@MetadataCreated"; + put ""; + put "/GetRepositories/Repositories/Repository/@MetadataCreated"; put "characterstring24"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@MetadataUpdated"; + put ""; + put "/GetRepositories/Repositories/Repository/@MetadataUpdated"; put "characterstring24"; put ''; put '
'; @@ -9160,28 +9281,32 @@ libname _XML_ clear; filename response temp; options noquotelenmax; proc metadata in= '$METAREPOSITORY - IdentityGroupSAS388 - - - - - ' - out=response; + IdentityGroupSAS388 + + + + +
' + out=response; run; filename sxlemap temp; data _null_; file sxlemap; put ''; - put "/GetMetadataObjects/Objects/IdentityGroup"; + put "/GetMetadataObjects/Objects/IdentityGroup"; + put ""; put ''; - put "/GetMetadataObjects/Objects/IdentityGroup/@Id"; + put "/GetMetadataObjects/Objects/IdentityGroup/@Id"; + put ""; put "characterstring32"; put ''; - put "/GetMetadataObjects/Objects/IdentityGroup/@Name"; + put "/GetMetadataObjects/Objects/IdentityGroup/@Name"; + put ""; put "characterstring256"; put ''; - put "/GetMetadataObjects/Objects/IdentityGroup/@Desc"; + put "/GetMetadataObjects/Objects/IdentityGroup/@Desc"; + put ""; put "characterstring500"; put '
'; run; @@ -9350,14 +9475,14 @@ run; /** - * Now we can extract the textstore - */ + * Now we can extract the textstore + */ filename __getdoc temp lrecl=10000000; proc metadata - in="$METAREPOSITORY - - SAS1" - out=__getdoc ; + in="$METAREPOSITORY + + SAS1" + out=__getdoc ; run; /* find the beginning of the text */ @@ -9378,47 +9503,47 @@ data _null_; /* read the content, byte by byte, resolving escaped chars */ filename __outdoc &outeng lrecl=100000; data _null_; - length filein 8 fileid 8; - filein = fopen("__getdoc","I",1,"B"); - fileid = fopen("__outdoc","O",1,"B"); - rec = "20"x; - length entity $6; - do while(fread(filein)=0); - x+1; - if x>&start then do; - rc = fget(filein,rec,1); - if rec='"' then leave; - else if rec="&" then do; - entity=rec; - do until (rec=";"); - if fread(filein) ne 0 then goto getout; - rc = fget(filein,rec,1); - entity=cats(entity,rec); + length filein 8 fileid 8; + filein = fopen("__getdoc","I",1,"B"); + fileid = fopen("__outdoc","O",1,"B"); + rec = "20"x; + length entity $6; + do while(fread(filein)=0); + x+1; + if x>&start then do; + rc = fget(filein,rec,1); + if rec='"' then leave; + else if rec="&" then do; + entity=rec; + do until (rec=";"); + if fread(filein) ne 0 then goto getout; + rc = fget(filein,rec,1); + entity=cats(entity,rec); + end; + select (entity); + when ('&' ) rec='&' ; + when ('<' ) rec='<' ; + when ('>' ) rec='>' ; + when (''') rec="'" ; + when ('"') rec='"' ; + when (' ') rec='0A'x; + when (' ') rec='0D'x; + when ('$' ) rec='$' ; + when (' ') rec='09'x; + otherwise putlog "%str(WARN)ING: missing value for " entity=; + end; + rc =fput(fileid, substr(rec,1,1)); + rc =fwrite(fileid); end; - select (entity); - when ('&' ) rec='&' ; - when ('<' ) rec='<' ; - when ('>' ) rec='>' ; - when (''') rec="'" ; - when ('"') rec='"' ; - when (' ') rec='0A'x; - when (' ') rec='0D'x; - when ('$' ) rec='$' ; - when (' ') rec='09'x; - otherwise putlog "%str(WARN)ING: missing value for " entity=; + else do; + rc =fput(fileid,rec); + rc =fwrite(fileid); end; - rc =fput(fileid, substr(rec,1,1)); - rc =fwrite(fileid); end; - else do; - rc =fput(fileid,rec); - rc =fwrite(fileid); - end; - end; - end; - getout: - rc=fclose(filein); - rc=fclose(fileid); + end; + getout: + rc=fclose(filein); + rc=fclose(fileid); run; %if &outeng=TEMP %then %do; @@ -9460,16 +9585,17 @@ filename __outdoc clear; combine with the tree= parameter. @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 showDesc= provide a non blank value to return stored process descriptions - @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). + @param showDesc= provide a non blank value to return stored process + descriptions + @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 - - stpuri - - stpname - - treeuri - - stpdesc (if requested) - - usageversion (if requested) + - stpuri + - stpname + - treeuri + - stpdesc (if requested) + - usageversion (if requested) @version 9.2 @author Allan Bowe @@ -9477,7 +9603,7 @@ filename __outdoc clear; **/ %macro mm_getstps( - tree= + tree= ,name= ,outds=work.mm_getstps ,mDebug=0 @@ -9547,7 +9673,7 @@ run; /** @file mm_gettableid.sas @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. Usage: @@ -9568,7 +9694,7 @@ run; **/ %macro mm_gettableid( - libref= + libref= ,ds= ,outds=work.mm_gettableid ,mDebug=0 @@ -9598,7 +9724,7 @@ data &outds; if type='DatabaseSchema' then tmpuri=usingpkguri; else tmpuri=uri; - + t=1; do while(metadata_getnasn(tmpuri, "Tables", t, tableuri)>0); t+1; @@ -9745,8 +9871,8 @@ run; @param mDebug= set to 1 to show debug messages in the log @returns outds dataset containing the following columns: - - treeuri - - treepath + - treeuri + - treepath @version 9.2 @author Allan Bowe @@ -9754,7 +9880,7 @@ run; **/ %macro mm_getTree( - tree= + tree= ,inds= ,outds=work.mm_getTree ,mDebug=0 @@ -9822,16 +9948,16 @@ run; filename response temp; /* get list of libraries */ proc metadata in= - ' - - SAS - - 2048 - - - $METAREPOSITORY - -' + ' + + SAS + + 2048 + + + $METAREPOSITORY + + ' out=response; run; @@ -9901,16 +10027,16 @@ libname _XML_ clear; filename response temp; proc metadata in= ' - $METAREPOSITORY - Person - SAS - 0 - - - - - - ' + $METAREPOSITORY + Person + SAS + 0 + + + + + +
' out=response; run; @@ -9918,7 +10044,8 @@ filename sxlemap temp; data _null_; file sxlemap; put ''; - put "/GetMetadataObjects/Objects/Person"; + put "/GetMetadataObjects/Objects/Person"; + put ""; put ''; put "/GetMetadataObjects/Objects/Person/@Id"; put "characterstring32"; @@ -9973,23 +10100,23 @@ filename __in temp lrecl=10000; filename __out temp lrecl=10000; filename __shake temp lrecl=10000; data _null_ ; - file __in ; - put '' ; - put '$METAREPOSITORY' ; - put 'TextStore' ; - put 'SAS' ; - put '388' ; - put '' ; - put ''; - put '' ; - put '' ; - put '' ; - put '' ; - put '' ; - put '' ; + file __in ; + put '' ; + put '$METAREPOSITORY' ; + put 'TextStore' ; + put 'SAS' ; + put '388' ; + put '' ; + put ''; + put '' ; + put '' ; + put '' ; + put '' ; + put '' ; + put '' ; run ; proc metadata in=__in out=__out verbose;run; @@ -10075,7 +10202,7 @@ filename __shake clear; %mend;/** @file mm_spkexport.sas @brief Creates an batch spk export command - @details Creates a script that will export everything in a metadata folder to + @details Creates a script that will export everything in a metadata folder to a specified location. If you have XCMD enabled, then you can use mmx_spkexport (which performs the actual export) @@ -10086,7 +10213,8 @@ filename __shake clear; Usage: %* 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; %* create sample text file as input to the macro; @@ -10108,7 +10236,7 @@ filename __shake clear; filename myref "/tmp/mmscript.sh"; %mm_spkexport(metaloc=%str(/my/meta/loc) - outref=myref + outref=myref ,cmdoutloc=%str(/tmp) ,cmdoutname=mmx ) @@ -10130,9 +10258,9 @@ filename __shake clear; @param secureref= fileref containing the username / password (should point to a file in a secure location). Leave blank to substitute $bash type vars. @param outref= fileref to which to write the command - @param cmdoutloc= the directory to which the command will write the SPK + @param cmdoutloc= the directory to which the command will write the SPK (default=WORK) - @param cmdoutname= the name of the spk / log files to create (will be + @param cmdoutname= the name of the spk / log files to create (will be identical just with .spk or .log extension) @version 9.4 @@ -10166,7 +10294,8 @@ filename __shake clear; %let port=%sysfunc(getoption(metaport)); %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) @@ -10208,7 +10337,8 @@ run; Usage: %* 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; %* export everything; @@ -10225,16 +10355,16 @@ run; %* with specific types; %mm_tree(root=%str(/my/folder) - ,types= - DeployedJob - ExternalFile - Folder - Folder.SecuredData - GeneratedTransform - InformationMap.Relational - Job - Library - Prompt + ,types= + DeployedJob + ExternalFile + Folder + Folder.SecuredData + GeneratedTransform + InformationMap.Relational + Job + Library + Prompt StoredProcess Table ,outds=morestuff) @@ -10246,8 +10376,8 @@ run; @param root= the parent folder under which to return all contents @param outds= the dataset to create that contains the list of directories - @param types= Space-seperated, unquoted list of types for filtering the - output. Special types: + @param types= Space-seperated, unquoted list of types for filtering the + output. Special types: * ALl - return all types (the default) * EXPORTABLE - return only the content types that can be exported in an SPK @@ -10257,7 +10387,7 @@ run; **/ %macro mm_tree( - root= + root= ,types=ALL ,outds=work.mm_tree )/*/STORE SOURCE*/; @@ -10277,12 +10407,12 @@ options noquotelenmax; filename response temp; /* get list of libraries */ proc metadata in= - '$METAREPOSITORY - TreeSAS - 384 - - ' - out=response; + '$METAREPOSITORY + TreeSAS + 384 + + ' + out=response; run; /* data _null_; @@ -10297,7 +10427,8 @@ filename sxlemap temp; data _null_; file sxlemap; put '
'; - put "/GetMetadataObjects/Objects/Tree"; + put "/GetMetadataObjects/Objects/Tree"; + put ""; put ''; put "/GetMetadataObjects/Objects/Tree/@Id"; put "characterstring64"; @@ -10323,7 +10454,7 @@ data &outds; path=cats('/',pname,path); tmpuri=parenturi; end; - + if path=:"&root"; %if "&types"="ALL" or ("&types" ne "ALL" and "&types" ne "Folder") %then %do; @@ -10622,8 +10753,8 @@ run; @param target= full path to the STP being deleted - @param type= Either WKS or STP depending on whether Workspace or Stored Process - type required + @param type= Either WKS or STP depending on whether Workspace or + Stored Process type required @version 9.4 @author Allan Bowe @@ -10636,8 +10767,8 @@ run; )/*/STORE SOURCE*/; /** - * Check STP does exist - */ + * Check STP does exist + */ %local cmtype; data _null_; length type uri $256; @@ -10663,7 +10794,8 @@ data _null_; n+1; rc=metadata_getattr(uri,"Name",name); if name='Stored Process' then do; - rc = METADATA_SETATTR(uri,'StoredText','' + rc = METADATA_SETATTR(uri,'StoredText' + ,'' !!''); @@ -10850,7 +10982,7 @@ run; **/ %macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y); -%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug +%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug sasjs_tables; %local i tempds; @@ -10925,7 +11057,7 @@ run; i+1; call symputx('wt'!!left(i),name,'l'); call symputx('wtcnt',i,'l'); - data _null_; file &fref encoding='utf-8'; + data _null_; file &fref encoding='utf-8'; put ",""WORK"":{"; %do i=1 %to &wtcnt; %let wt=&&wt&i; @@ -11037,7 +11169,8 @@ Usage: run; 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 ,outspkpath=%str(/tmp) ) @@ -11051,7 +11184,7 @@ Usage: @param metaloc= the metadata folder to export @param secureref= fileref containing the username / password (should point to a file in a secure location) - @param outspkname= name of the spk to be created (default is mmxport). + @param outspkname= name of the spk to be created (default is mmxport). @param outspkpath= directory in which to create the SPK. Default is WORK. @version 9.4 @@ -11073,7 +11206,8 @@ Usage: /* get creds */ %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) @@ -11262,7 +11396,8 @@ options noquotelenmax; 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"; + 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; @@ -11411,7 +11546,7 @@ proc http method='GET' 'Accept'='application/vnd.sas.collection+json' 'Accept-Language'='string'; %if &debug=1 %then %do; - debug level = 3; + debug level = 3; %end; run; /*data _null_;infile &fname2;input;putlog _infile_;run;*/ @@ -11448,13 +11583,13 @@ data _null_; file &fname3 TERMSTR=' '; length string $32767; string=cats('{"version": 0,"name":"' - ,"&name" - ,'","type":"Compute","parameters":[{"name":"_addjesbeginendmacros"' + ,"&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}'); + ,context,',"type":"CHARACTER","label":"Context Name","required": false}'); end; string=cats(string,'],"code":"'); put string; @@ -11524,7 +11659,7 @@ proc http method='POST' %end; "Accept"="application/vnd.sas.job.definition+json"; %if &debug=1 %then %do; - debug level = 3; + debug level = 3; %end; run; /*data _null_;infile &fname4;input;putlog _infile_;run;*/ @@ -11569,7 +11704,8 @@ run; 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"; + filename mc url + "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; %inc mc; %* Step 2 - Create some code and add it to a web service; @@ -11582,7 +11718,7 @@ run; run; %* send data back; %webout(OPEN) - %webout(ARR,example1) * Array format, fast, suitable for large tables ; + %webout(ARR,example1) * Array format, fast, suitable for large tables; %webout(OBJ,example2) * Object format, easier to work with ; %webout(CLOSE) ;;;; @@ -11616,7 +11752,8 @@ run; adapter, add a (different) fileref here. @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 + 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, source: https://github.com/sasjs/core @@ -11727,7 +11864,7 @@ proc http method='GET' 'Accept'='application/vnd.sas.collection+json' 'Accept-Language'='string'; %if &debug=1 %then %do; - debug level = 3; + debug level = 3; %end; run; /*data _null_;infile &fname2;input;putlog _infile_;run;*/ @@ -11764,23 +11901,23 @@ data _null_; file &fname3 TERMSTR=' '; length string $32767; string=cats('{"version": 0,"name":"' - ,"&name" - ,'","type":"Compute","parameters":[{"name":"_addjesbeginendmacros"' + ,"&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}'); + ,context,',"type":"CHARACTER","label":"Context Name","required": false}'); end; string=cats(string,'],"code":"'); put string; run; /** - * Add webout macro - * These put statements are auto generated - to change the macro, change the - * source (mv_webout) and run `build.py` - */ + * Add webout macro + * These put statements are auto generated - to change the macro, change the + * source (mv_webout) and run `build.py` + */ filename sasjs temp lrecl=3000; data _null_; file sasjs; @@ -11820,7 +11957,8 @@ data _null_; put ' %end; '; put ' data _null_;file &jref mod ; '; 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 ' by varnum; '; put ' '; @@ -11859,7 +11997,8 @@ data _null_; put ' %end; '; put ' %end; '; 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 ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; '; put ' set &tempds; '; @@ -11974,7 +12113,8 @@ data _null_; put ' if _n_=1 then call symputx(''input_statement'',_infile_); '; put ' list; '; 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 ' run; '; put ' %end; '; @@ -12006,7 +12146,7 @@ data _null_; put ' /* setup webout */ '; put ' OPTIONS NOBOMFILE; '; put ' %if "X&SYS_JES_JOB_URI.X"="XX" %then %do; '; - put ' filename _webout temp lrecl=999999 mod; '; + put ' filename _webout temp lrecl=999999 mod; '; put ' %end; '; put ' %else %do; '; put ' filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" '; @@ -12015,7 +12155,8 @@ data _null_; put ' '; put ' /* setup temp ref */ '; 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 ' '; put ' /* setup json */ '; @@ -12180,7 +12321,7 @@ proc http method='POST' %end; "Accept"="application/vnd.sas.job.definition+json"; %if &debug=1 %then %do; - debug level = 3; + debug level = 3; %end; run; /*data _null_;infile &fname4;input;putlog _infile_;run;*/ @@ -12656,27 +12797,27 @@ filename &fname1 clear; filename &fname2 clear; libname &libref1 clear; -%mend; /** - @file mv_getaccesstoken.sas - @brief deprecated - replaced by mv_tokenrefresh.sas +%mend;/** + @file mv_getaccesstoken.sas + @brief deprecated - replaced by mv_tokenrefresh.sas - @version VIYA V.03.04 - @author Allan Bowe, source: https://github.com/sasjs/core + @version VIYA V.03.04 + @author Allan Bowe, source: https://github.com/sasjs/core -

SAS Macros

- @li mv_tokenrefresh.sas +

SAS Macros

+ @li mv_tokenrefresh.sas - **/ +**/ - %macro mv_getaccesstoken(client_id=someclient - ,client_secret=somesecret - ,grant_type=authorization_code - ,code= - ,user= - ,pass= - ,access_token_var=ACCESS_TOKEN - ,refresh_token_var=REFRESH_TOKEN - ); +%macro mv_getaccesstoken(client_id=someclient + ,client_secret=somesecret + ,grant_type=authorization_code + ,code= + ,user= + ,pass= + ,access_token_var=ACCESS_TOKEN + ,refresh_token_var=REFRESH_TOKEN + ); %mv_tokenrefresh(client_id=&client_id ,client_secret=&client_secret @@ -12687,22 +12828,22 @@ libname &libref1 clear; ,refresh_token_var=&refresh_token_var ) -%mend; /** - @file - @brief deprecated - replaced by mv_registerclient.sas +%mend;/** + @file + @brief deprecated - replaced by mv_registerclient.sas - @version VIYA V.03.04 - @author Allan Bowe, source: https://github.com/sasjs/core + @version VIYA V.03.04 + @author Allan Bowe, source: https://github.com/sasjs/core -

SAS Macros

- @li mv_registerclient.sas +

SAS Macros

+ @li mv_registerclient.sas - **/ +**/ - %macro mv_getapptoken(client_id=someclient - ,client_secret=somesecret - ,grant_type=authorization_code - ); +%macro mv_getapptoken(client_id=someclient + ,client_secret=somesecret + ,grant_type=authorization_code + ); %mv_registerclient(client_id=&client_id ,client_secret=&client_secret @@ -13124,7 +13265,7 @@ libname &libref1 clear; Example: %mv_getjobcode( - path=/Public/jobs + path=/Public/jobs ,name=some_job ,outfile=/tmp/some_job.sas ) @@ -13248,7 +13389,7 @@ data _null_; outfile:write(job) io.close(infile) io.close(outfile) - '; + '; run; %inc "&fpath3..lua"; /* export to desired destination */ @@ -13279,7 +13420,8 @@ filename &fname3 clear; First, compile the 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; Next, create a job (in this case, a web service): @@ -13443,7 +13585,7 @@ data _null_; outfile:write(logloc) io.close(infile) io.close(outfile) - '; + '; run; %inc "&fpath3..lua"; /* get log path*/ @@ -13497,7 +13639,7 @@ data _null_; io.input(infile) local resp=json.decode(io.read()) for i, v in pairs(resp["items"]) do - outfile:write(v.line,"\n") + outfile:write(v.line,"\n") end io.close(infile) io.close(outfile) @@ -13703,27 +13845,27 @@ run; filename &fname0 clear; %mend; - /** - @file mv_getrefreshtoken.sas - @brief deprecated - replaced by mv_tokenauth.sas +/** + @file mv_getrefreshtoken.sas + @brief deprecated - replaced by mv_tokenauth.sas - @version VIYA V.03.04 - @author Allan Bowe, source: https://github.com/sasjs/core + @version VIYA V.03.04 + @author Allan Bowe, source: https://github.com/sasjs/core -

SAS Macros

- @li mv_tokenauth.sas +

SAS Macros

+ @li mv_tokenauth.sas - **/ +**/ - %macro mv_getrefreshtoken(client_id=someclient - ,client_secret=somesecret - ,grant_type=authorization_code - ,code= - ,user= - ,pass= - ,access_token_var=ACCESS_TOKEN - ,refresh_token_var=REFRESH_TOKEN - ); +%macro mv_getrefreshtoken(client_id=someclient + ,client_secret=somesecret + ,grant_type=authorization_code + ,code= + ,user= + ,pass= + ,access_token_var=ACCESS_TOKEN + ,refresh_token_var=REFRESH_TOKEN + ); %mv_tokenauth(client_id=&client_id ,client_secret=&client_secret @@ -13802,7 +13944,7 @@ proc http method='GET' out=&fname1 &oauth_bearer url="&base_uri/identities/users/&user/memberships?limit=10000"; headers %if &grant_type=authorization_code %then %do; - "Authorization"="Bearer &&&access_token_var" + "Authorization"="Bearer &&&access_token_var" %end; "Accept"="application/json"; run; @@ -13865,11 +14007,11 @@ libname &libref1 clear; @param access_token_var= The global macro variable to contain the access token @param grant_type= valid values: - * password - * authorization_code - * detect - will check if access_token exists, if not will use sas_services if - a SASStudioV session else authorization_code. Default option. - * sas_services - will use oauth_bearer=sas_services + * password + * authorization_code + * detect - will check if access_token exists, if not will use sas_services if + a SASStudioV session else authorization_code. Default option. + * sas_services - will use oauth_bearer=sas_services @param outds= The library.dataset to be created that contains the list of groups @@ -14127,7 +14269,7 @@ libname &libref; @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`. + 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. @@ -14211,15 +14353,17 @@ libname &libref; run; - @param [in] access_token_var= The global macro variable to contain the access token + @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 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] 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 @@ -14299,7 +14443,7 @@ select count(*) into: missings where flow_id is null or _program is null; %mp_abort(iftrue=(&missings>0) ,mac=&sysmacroname - ,msg=%str(input dataset contains &missings missing values for FLOW_ID or _PROGRAM) + ,msg=%str(input dataset has &missings missing values for FLOW_ID or _PROGRAM) ) %if %mf_nobs(&inds)=0 %then %do; @@ -14398,7 +14542,8 @@ data;run;%let jdswaitfor=&syslast; %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); + %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 @@ -14448,7 +14593,8 @@ data;run;%let jdswaitfor=&syslast; /* 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; + %put looping flow &fid again - &completed of &jcnt jobs completed, + &concurrency jobs running; %end; %end; %end; @@ -14687,7 +14833,7 @@ run; %else %let SYSCC=5; %put %str(ERR)OR: Job &&jobname&i. did not complete successfully. &stateDetails; %return; - %end; + %end; %end; %end; @@ -14709,15 +14855,17 @@ filename &fname0 clear; @details When building apps on SAS Viya, an client id and secret is required. This macro will obtain the Consul Token and use that to call the Web Service. - more info: https://developer.sas.com/reference/auth/#register - and: http://proc-x.com/2019/01/authentication-to-sas-viya-a-couple-of-approaches/ + more info: https://developer.sas.com/reference/auth/#register + and: + http://proc-x.com/2019/01/authentication-to-sas-viya-a-couple-of-approaches The default viyaroot location is /opt/sas/viya/config Usage: %* 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; %* specific client with just openid scope; @@ -14738,7 +14886,8 @@ filename &fname0 clear; @param client_id= The client name. Auto generated if blank. @param client_secret= Client secret Auto generated if client is blank. @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 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) @@ -14783,15 +14932,16 @@ filename &fname0 clear; ,refresh_token_validity=DEFAULT ,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= Generated by %mf_getuser() on %sysfunc(datetime(),datetime19.) using SASjs; options noquotelenmax; /* first, get consul token needed to get client id / secret */ +%let tokloc=/etc/SASSecurityCertificateFramework/tokens/consul/default; data _null_; - infile "%mf_loc(VIYACONFIG)/etc/SASSecurityCertificateFramework/tokens/consul/default/client.token"; + infile "%mf_loc(VIYACONFIG)&tokloc/client.token"; input token:$64.; call symputx('consul_token',token); run; @@ -14802,8 +14952,9 @@ run; /* request the client details */ %let fname1=%mf_getuniquefileref(); proc http method='POST' out=&fname1 - url="&base_uri/SASLogon/oauth/clients/consul?callback=false%str(&)serviceId=app"; - headers "X-Consul-Token"="&consul_token"; + url="&base_uri/SASLogon/oauth/clients/consul?callback=false%str(&)%trim( + )serviceId=app"; + headers "X-Consul-Token"="&consul_token"; run; %let libref=%mf_getuniquelibref(); @@ -14816,8 +14967,8 @@ data _null_; run; /** - * register the new client - */ + * register the new client + */ %let fname2=%mf_getuniquefileref(); %if x&client_id.x=xx %then %do; %let client_id=client_%sysfunc(ranuni(0),hex16.); @@ -14827,7 +14978,8 @@ run; %let scopes=%sysfunc(coalescec(&scopes,openid)); %let scopes=%mf_getquotedstr(&scopes,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_; file &fname2; @@ -14844,9 +14996,11 @@ data _null_; if reqd_groups = '""' then reqd_groups =''; else reqd_groups=cats(',"required_user_groups":[',reqd_groups,']'); 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')); - 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 clientsecret ; @@ -14911,10 +15065,12 @@ run; %put GRANT_TYPE=&grant_type; %put; %if %index(%superq(grant_type),authorization_code) %then %do; - /* cannot use base_uri here as it includes the protocol which may be incorrect externally */ - %put NOTE: The developer must also register below and select 'openid' to get the grant code:; + /* cannot use base_uri here as it includes the protocol which may be incorrect + externally */ + %put NOTE: Visit the link below and select 'openid' to get the grant code:; %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- ; %end; @@ -14954,7 +15110,8 @@ libname &libref clear; 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; @@ -14970,13 +15127,15 @@ libname &libref clear; @param outds= A dataset containing access_token and refresh_token @param client_id= The client name @param client_secret= client secret - @param grant_type= valid values are "password" or "authorization_code" (unquoted). - The default is authorization_code. - @param code= If grant_type=authorization_code then provide the necessary code here + @param grant_type= valid values are "password" or "authorization_code" + (unquoted). The default is authorization_code. + @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 pass= If grant_type=password then provide the password here @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 @version VIYA V.03.04 @@ -15027,7 +15186,8 @@ libname &libref clear; ,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 ,msg=%str(username / password required) ) @@ -15038,7 +15198,7 @@ libname &libref clear; data _null_; file &fref1; if "&grant_type"='authorization_code' then string=cats( - 'grant_type=authorization_code&code=',symget('code')); + 'grant_type=authorization_code&code=',symget('code')); else string=cats('grant_type=password&username=',symget('user') ,'&password=',symget(pass)); call symputx('grantstring',cats("'",string,"'")); @@ -15046,8 +15206,8 @@ run; /*data _null_;infile &fref1;input;put _infile_;run;*/ /** - * Request access token - */ + * Request access token + */ %if &base_uri=#NOTSET# %then %let base_uri=%mf_getplatform(VIYARESTAPI); %let fref2=%mf_getuniquefileref(); @@ -15062,8 +15222,8 @@ run; /*data _null_;infile &fref2;input;put _infile_;run;*/ /** - * Extract access / refresh tokens - */ + * Extract access / refresh tokens + */ %let libref=%mf_getuniquelibref(); libname &libref JSON fileref=&fref2; @@ -15114,12 +15274,13 @@ filename &fref2 clear; @param outds= A dataset containing access_token and refresh_token @param client_id= The client name (alternative to inds) @param client_secret= client secret (alternative to inds) - @param grant_type= valid values are "password" or "authorization_code" (unquoted). - The default is authorization_code. + @param grant_type= valid values are "password" or "authorization_code" + (unquoted). The default is authorization_code. @param user= If grant_type=password then provide the username 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 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 @author Allan Bowe, source: https://github.com/sasjs/core @@ -15154,7 +15315,8 @@ options noquotelenmax; ,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 ,msg=%str(username / password required) ) @@ -15174,8 +15336,8 @@ options noquotelenmax; ) /** - * Request access token - */ + * Request access token + */ %local base_uri; /* location of rest apis */ %let base_uri=%mf_getplatform(VIYARESTAPI); @@ -15193,8 +15355,8 @@ run; /*data _null_;infile &fref1;input;put _infile_;run;*/ /** - * Extract access / refresh tokens - */ + * Extract access / refresh tokens + */ %let libref=%mf_getuniquelibref(); libname &libref JSON fileref=&fref1; @@ -15321,7 +15483,8 @@ filename &fref1 clear; if _n_=1 then call symputx('input_statement',_infile_); list; data &table; - infile "%sysfunc(pathname(work))/&table..csv" firstobs=2 dsd termstr=crlf; + infile "%sysfunc(pathname(work))/&table..csv" firstobs=2 dsd + termstr=crlf; input &input_statement; run; %end; @@ -15353,7 +15516,7 @@ filename &fref1 clear; /* setup webout */ OPTIONS NOBOMFILE; %if "X&SYS_JES_JOB_URI.X"="XX" %then %do; - filename _webout temp lrecl=999999 mod; + filename _webout temp lrecl=999999 mod; %end; %else %do; filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" @@ -15362,7 +15525,8 @@ filename &fref1 clear; /* setup temp ref */ %if %upcase(&fref) ne _WEBOUT %then %do; - filename &fref temp lrecl=999999 permission='A::u::rwx,A::g::rw-,A::o::---' mod; + filename &fref temp lrecl=999999 permission='A::u::rwx,A::g::rw-,A::o::---' + mod; %end; /* setup json */ diff --git a/base/mf_abort.sas b/base/mf_abort.sas index 4a309c6..2188626 100644 --- a/base/mf_abort.sas +++ b/base/mf_abort.sas @@ -3,7 +3,7 @@ @brief abort gracefully according to context @details Do not use directly! See bottom of explanation for details. - Configures an abort mechanism according to site specific policies or the + Configures an abort mechanism according to site specific policies or the particulars of an environment. For instance, can stream custom results back to the client in an STP Web App context, or completely stop in the case of a batch run. @@ -49,7 +49,10 @@ input; putlog _infile_; i=1; 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_); logonce+1; end; @@ -112,21 +115,22 @@ %let syscc=0; %if %symexist(SYS_JES_JOB_URI) %then %do; /* 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)); %end; %else %do; data _null_; if symexist('sysprocessmode') - then if symget("sysprocessmode")="SAS Stored Process Server" - then rc=stpsrvset('program error', 0); + then if symget("sysprocessmode")="SAS Stored Process Server" + then rc=stpsrvset('program error', 0); run; %end; /** - * endsas is reliable but kills some deployments. - * Abort variants are ungraceful (non zero return code) - * This approach lets SAS run silently until the end :-) - */ + * endsas is reliable but kills some deployments. + * Abort variants are ungraceful (non zero return code) + * This approach lets SAS run silently until the end :-) + */ %put _all_; filename skip temp; data _null_; diff --git a/base/mf_getattrc.sas b/base/mf_getattrc.sas index f29b00c..99eb78e 100644 --- a/base/mf_getattrc.sas +++ b/base/mf_getattrc.sas @@ -17,7 +17,7 @@ **/ %macro mf_getattrc( - libds + libds ,attr )/*/STORE SOURCE*/; %local dsid rc; diff --git a/base/mf_getattrn.sas b/base/mf_getattrn.sas index b86b117..933a975 100755 --- a/base/mf_getattrn.sas +++ b/base/mf_getattrn.sas @@ -8,7 +8,7 @@ @param libds library.dataset @param attr Common values are NLOBS and NVARS, full list in [documentation]( - http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212040.htm) + http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212040.htm) @return output returns result of the attrn value supplied, or -1 and log message if error. @@ -17,7 +17,7 @@ **/ %macro mf_getattrn( - libds + libds ,attr )/*/STORE SOURCE*/; %local dsid rc; diff --git a/base/mf_getengine.sas b/base/mf_getengine.sas index e17d369..a45b801 100755 --- a/base/mf_getengine.sas +++ b/base/mf_getengine.sas @@ -32,7 +32,9 @@ /* in case the parameter is a libref.tablename, pull off just the libref */ %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; %let engnum=%sysfunc(varnum(&dsid,ENGINE)); %let rc=%sysfunc(fetch(&dsid)); @@ -41,7 +43,7 @@ %let rc= %sysfunc(close(&dsid)); %end; - &engine + &engine %mend; diff --git a/base/mf_getfilesize.sas b/base/mf_getfilesize.sas index c49453a..02785a1 100644 --- a/base/mf_getfilesize.sas +++ b/base/mf_getfilesize.sas @@ -38,7 +38,7 @@ %let rc=%sysfunc(filename(fref)); %if &format=NO %then %do; - &bytes + &bytes %end; %else %do; %sysfunc(INPUTN(&bytes, best.),sizekmg.) diff --git a/base/mf_getkeyvalue.sas b/base/mf_getkeyvalue.sas index b3e9136..b1e21cd 100644 --- a/base/mf_getkeyvalue.sas +++ b/base/mf_getkeyvalue.sas @@ -16,7 +16,7 @@ %macro mf_getkeyvalue(key,libds=work.mp_setkeyvalue )/*/STORE SOURCE*/; - %local ds dsid key valc valn type rc; +%local ds dsid key valc valn type rc; %let dsid=%sysfunc(open(&libds(where=(key="&key")))); %syscall set(dsid); %let rc = %sysfunc(fetch(&dsid)); diff --git a/base/mf_getplatform.sas b/base/mf_getplatform.sas index 594e214..6ef0288 100644 --- a/base/mf_getplatform.sas +++ b/base/mf_getplatform.sas @@ -23,7 +23,7 @@ %local a b c; %if &switch.NONE=NONE %then %do; %if %symexist(sysprocessmode) %then %do; - %if "&sysprocessmode"="SAS Object Server" + %if "&sysprocessmode"="SAS Object Server" or "&sysprocessmode"= "SAS Compute Server" %then %do; SASVIYA %end; diff --git a/base/mf_getuser.sas b/base/mf_getuser.sas index d9c5476..10a0f77 100755 --- a/base/mf_getuser.sas +++ b/base/mf_getuser.sas @@ -2,7 +2,7 @@ @file @brief Returns a userid according to session context @details In a workspace session, a user is generally represented by - &sysuserid or SYS_COMPUTE_SESSION_OWNER if it exists. + &sysuserid or SYS_COMPUTE_SESSION_OWNER if it exists. In a Stored Process session, &sysuserid resolves to a system account (default=sassrv) and instead there are several metadata username variables to choose from (_metauser, _metaperson @@ -12,7 +12,7 @@ %let user= %mf_getUser(); %put &user; - + @param type - do not use, may be deprecated in a future release @return SYSUSERID (if workspace server) diff --git a/base/mf_getvalue.sas b/base/mf_getvalue.sas index 7f03b7d..9c5608e 100644 --- a/base/mf_getvalue.sas +++ b/base/mf_getvalue.sas @@ -20,7 +20,7 @@ %macro mf_getvalue(libds,variable,filter=1 )/*/STORE SOURCE*/; - %if %mf_getattrn(&libds,NLOBS)>0 %then %do; + %if %mf_getattrn(&libds,NLOBS)>0 %then %do; %local dsid rc &variable; %let dsid=%sysfunc(open(&libds(where=(&filter)))); %syscall set(dsid); diff --git a/base/mf_getvarformat.sas b/base/mf_getvarformat.sas index 64a1c4a..63aacdd 100755 --- a/base/mf_getvarformat.sas +++ b/base/mf_getvarformat.sas @@ -5,9 +5,9 @@ Usage: data test; - format str1 $1. num1 datetime19.; - str2='hello mum!'; num2=666; - stop; + format str1 $1. num1 datetime19.; + str2='hello mum!'; num2=666; + stop; run; %put %mf_getVarFormat(test,str1); %put %mf_getVarFormat(work.test,num1); @@ -45,9 +45,9 @@ /* Get variable format */ %if(&vnum > 0) %then %let vformat=%sysfunc(varfmt(&dsid, &vnum)); %else %do; - %put NOTE: Variable &var does not exist in &libds; - %let rc = %sysfunc(close(&dsid)); - %return; + %put NOTE: Variable &var does not exist in &libds; + %let rc = %sysfunc(close(&dsid)); + %return; %end; %end; %else %do; diff --git a/base/mf_getvarlen.sas b/base/mf_getvarlen.sas index 82709ba..de8fd70 100644 --- a/base/mf_getvarlen.sas +++ b/base/mf_getvarlen.sas @@ -5,8 +5,8 @@ Usage: data test; - format str $1. num datetime19.; - stop; + format str $1. num datetime19.; + stop; run; %put %mf_getVarLen(test,str); %put %mf_getVarLen(work.test,num); @@ -39,8 +39,8 @@ /* Get variable format */ %if(&vnum > 0) %then %let vlen = %sysfunc(varlen(&dsid, &vnum)); %else %do; - %put NOTE: Variable &var does not exist in &libds; - %let vlen = %str( ); + %put NOTE: Variable &var does not exist in &libds; + %let vlen = %str( ); %end; %end; %else %put dataset &libds not opened! (rc=&dsid); diff --git a/base/mf_getvarnum.sas b/base/mf_getvarnum.sas index 97ed241..20681e8 100755 --- a/base/mf_getvarnum.sas +++ b/base/mf_getvarnum.sas @@ -6,8 +6,8 @@ Usage: data work.test; - format str $1. num datetime19.; - stop; + format str $1. num datetime19.; + stop; run; %put %mf_getVarNum(work.test,str); %put %mf_getVarNum(work.test,num); @@ -39,8 +39,8 @@ returns: /* Get variable number */ %let vnum = %sysfunc(varnum(&dsid, &var)); %if(&vnum <= 0) %then %do; - %put NOTE: Variable &var does not exist in &libds; - %let vnum = %str( ); + %put NOTE: Variable &var does not exist in &libds; + %let vnum = %str( ); %end; %end; %else %put dataset &ds not opened! (rc=&dsid); diff --git a/base/mf_getvartype.sas b/base/mf_getvartype.sas index fc9bc36..ebd2238 100755 --- a/base/mf_getvartype.sas +++ b/base/mf_getvartype.sas @@ -5,8 +5,8 @@ Usage: data test; - length str $1. num 8.; - stop; + length str $1. num 8.; + stop; run; %put %mf_getvartype(test,str); %put %mf_getvartype(work.test,num); @@ -35,8 +35,8 @@ Usage: /* Get variable type (C/N) */ %if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.)); %else %do; - %put NOTE: Variable &var does not exist in &libds; - %let vtype = %str( ); + %put NOTE: Variable &var does not exist in &libds; + %let vtype = %str( ); %end; %end; %else %put dataset &libds not opened! (rc=&dsid); diff --git a/base/mf_isblank.sas b/base/mf_isblank.sas index 4625506..ea324b2 100644 --- a/base/mf_isblank.sas +++ b/base/mf_isblank.sas @@ -6,12 +6,13 @@ %sysevalf(%superq(param)=,boolean) Usage: - - %put mf_isblank(&var); - inspiration: https://support.sas.com/resources/papers/proceedings09/022-2009.pdf + %put mf_isblank(&var); - @param param VALUE to be checked + inspiration: + https://support.sas.com/resources/papers/proceedings09/022-2009.pdf + + @param param VALUE to be checked @return output returns 1 (if blank) else 0 diff --git a/base/mf_isdir.sas b/base/mf_isdir.sas index 6fa9add..6018412 100644 --- a/base/mf_isdir.sas +++ b/base/mf_isdir.sas @@ -6,7 +6,8 @@ %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 @@ -17,17 +18,17 @@ %macro mf_isdir(path )/*/STORE SOURCE*/; - %local rc did is_directory fref_t; + %local rc did is_directory fref_t; - %let is_directory = 0; - %let rc = %sysfunc(filename(fref_t, %superq(path))); - %let did = %sysfunc(dopen(&fref_t.)); - %if &did. ^= 0 %then %do; - %let is_directory = 1; - %let rc = %sysfunc(dclose(&did.)); - %end; - %let rc = %sysfunc(filename(fref_t)); + %let is_directory = 0; + %let rc = %sysfunc(filename(fref_t, %superq(path))); + %let did = %sysfunc(dopen(&fref_t.)); + %if &did. ^= 0 %then %do; + %let is_directory = 1; + %let rc = %sysfunc(dclose(&did.)); + %end; + %let rc = %sysfunc(filename(fref_t)); - &is_directory + &is_directory %mend; \ No newline at end of file diff --git a/base/mf_mkdir.sas b/base/mf_mkdir.sas index fb1ff89..5e6b302 100755 --- a/base/mf_mkdir.sas +++ b/base/mf_mkdir.sas @@ -46,8 +46,8 @@ Usage: */ %if (%length(&dir) gt %length(&child)) %then %do; - %let parent = %substr(&dir, 1, %length(&dir)-%length(&child)); - %mf_mkdir(&parent) + %let parent = %substr(&dir, 1, %length(&dir)-%length(&child)); + %mf_mkdir(&parent) %end; /* @@ -56,11 +56,11 @@ Usage: %let dname = %sysfunc(dcreate(&child, &parent)); %if (%bquote(&dname) eq ) %then %do; - %put %str(ERR)OR: could not create &parent + &child; - %abort cancel; + %put %str(ERR)OR: could not create &parent + &child; + %abort cancel; %end; %else %do; - %put Directory created: &dir; + %put Directory created: &dir; %end; %end; /* exit quietly if directory did exist.*/ diff --git a/base/mf_mval.sas b/base/mf_mval.sas index 23a8f73..c623413 100644 --- a/base/mf_mval.sas +++ b/base/mf_mval.sas @@ -1,8 +1,9 @@ /** @file mf_mval.sas @brief Returns a macro variable value if the variable exists - @details Use this macro to avoid repetitive use of `%if %symexist(MACVAR) %then` - type logic. + @details + Use this macro to avoid repetitive use of `%if %symexist(MACVAR) %then` + type logic. Usage: %if %mf_mval(maynotexist)=itdid %then %do; diff --git a/base/mf_trimstr.sas b/base/mf_trimstr.sas index 24bc05f..e8c19f2 100644 --- a/base/mf_trimstr.sas +++ b/base/mf_trimstr.sas @@ -1,7 +1,7 @@ /** @file mf_trimstr.sas @brief Removes character(s) from the end, if they exist - @details If the designated characters exist at the end of the string, they + @details If the designated characters exist at the end of the string, they are removed %put %mf_trimstr(/blah/,/); * /blah; @@ -12,7 +12,8 @@ @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 diff --git a/base/mf_verifymacvars.sas b/base/mf_verifymacvars.sas index 76170b6..527993a 100755 --- a/base/mf_verifymacvars.sas +++ b/base/mf_verifymacvars.sas @@ -35,7 +35,7 @@ %macro mf_verifymacvars( - verifyVars /* list of macro variable NAMES */ + verifyVars /* list of macro variable NAMES */ ,makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */ ,mAbort=SOFT )/*/STORE SOURCE*/; diff --git a/base/mf_wordsinstr1butnotstr2.sas b/base/mf_wordsinstr1butnotstr2.sas index 849dc09..f2966f4 100755 --- a/base/mf_wordsinstr1butnotstr2.sas +++ b/base/mf_wordsinstr1butnotstr2.sas @@ -6,7 +6,7 @@ Usage: %let x= %mf_wordsInStr1ButNotStr2( - Str1=blah sss blaaah brah bram boo + Str1=blah sss blaaah brah bram boo ,Str2= blah blaaah brah ssss ); @@ -24,8 +24,8 @@ **/ %macro mf_wordsInStr1ButNotStr2( - Str1= /* string containing words to extract */ - ,Str2= /* used to compare with the extract string */ + Str1= /* string containing words to extract */ + ,Str2= /* used to compare with the extract string */ )/*/STORE SOURCE*/; %local count_base count_extr i i2 extr_word base_word match outvar; diff --git a/base/mp_abort.sas b/base/mp_abort.sas index 64bdf7f..5883823 100644 --- a/base/mp_abort.sas +++ b/base/mp_abort.sas @@ -6,11 +6,11 @@ results back to the client in an STP Web App context, or completely stop in the case of a batch run. - Using SAS Abort Cancel mechanisms can cause hung sessions in some Stored Process - environments. This macro takes a unique approach - we set the SAS syscc to 0, - run `stpsrvset('program error', 0)` (if SAS 9) and then - we open a macro - but don't close it! This provides a graceful abort for SAS web services in all - web enabled environments. + Using SAS Abort Cancel mechanisms can cause hung sessions in some Stored + Process environments. This macro takes a unique approach - we set the SAS + syscc to 0, run `stpsrvset('program error', 0)` (if SAS 9) and then - we open + a macro but don't close it! This provides a graceful abort for SAS web + services in all web enabled environments. @param mac= to contain the name of the calling macro @param msg= message to be returned @@ -48,7 +48,10 @@ input; putlog _infile_; i=1; 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_); logonce+1; end; @@ -129,15 +132,15 @@ %if %symexist(_metaport) %then %do; data _null_; if symexist('sysprocessmode') - then if symget("sysprocessmode")="SAS Stored Process Server" - then rc=stpsrvset('program error', 0); + then if symget("sysprocessmode")="SAS Stored Process Server" + then rc=stpsrvset('program error', 0); run; %end; /** - * endsas is reliable but kills some deployments. - * Abort variants are ungraceful (non zero return code) - * This approach lets SAS run silently until the end :-) - */ + * endsas is reliable but kills some deployments. + * Abort variants are ungraceful (non zero return code) + * This approach lets SAS run silently until the end :-) + */ %put _all_; filename skip temp; data _null_; diff --git a/base/mp_binarycopy.sas b/base/mp_binarycopy.sas index 599c372..58a8c85 100755 --- a/base/mp_binarycopy.sas +++ b/base/mp_binarycopy.sas @@ -2,9 +2,10 @@ @file @brief Copy any file using binary input / output streams @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 - default filerefs can be modified. - Based on http://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file + os-independent method to copy files. In case of naming collision, the + default filerefs can be modified. + Based on: + https://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file %mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout) @@ -19,12 +20,12 @@ **/ %macro mp_binarycopy( - inloc= /* full path and filename of the object to be copied */ + inloc= /* full path and filename of the object to be copied */ ,outloc= /* full path and filename of object to be created */ ,inref=____in /* override default to use own filerefs */ ,outref=____out /* override default to use own filerefs */ )/*/STORE SOURCE*/; - /* these IN and OUT filerefs can point to anything */ + /* these IN and OUT filerefs can point to anything */ %if &inref = ____in %then %do; filename &inref &inloc lrecl=1048576 ; %end; @@ -32,20 +33,20 @@ filename &outref &outloc lrecl=1048576 ; %end; - /* copy the file byte-for-byte */ - data _null_; - length filein 8 fileid 8; - filein = fopen("&inref",'I',1,'B'); - fileid = fopen("&outref",'O',1,'B'); - rec = '20'x; - do while(fread(filein)=0); - rc = fget(filein,rec,1); - rc = fput(fileid, rec); - rc =fwrite(fileid); - end; - rc = fclose(filein); - rc = fclose(fileid); - run; + /* copy the file byte-for-byte */ + data _null_; + length filein 8 fileid 8; + filein = fopen("&inref",'I',1,'B'); + fileid = fopen("&outref",'O',1,'B'); + rec = '20'x; + do while(fread(filein)=0); + rc = fget(filein,rec,1); + rc = fput(fileid, rec); + rc =fwrite(fileid); + end; + rc = fclose(filein); + rc = fclose(fileid); + run; %if &inref = ____in %then %do; filename &inref clear; %end; diff --git a/base/mp_cleancsv.sas b/base/mp_cleancsv.sas index ae009f7..c98c9d0 100644 --- a/base/mp_cleancsv.sas +++ b/base/mp_cleancsv.sas @@ -2,10 +2,10 @@ @file mp_cleancsv.sas @brief Fixes embedded cr / lf / crlf in CSV @details CSVs will sometimes contain lf or crlf within quotes (eg when - saved by excel). When the termstr is ALSO lf or crlf that can be tricky - to process using SAS defaults. - This macro converts any csv to follow the convention of a windows excel file, - applying CRLF line endings and converting embedded cr and crlf to lf. + saved by excel). When the termstr is ALSO lf or crlf that can be tricky + to process using SAS defaults. + This macro converts any csv to follow the convention of a windows excel file, + applying CRLF line endings and converting embedded cr and crlf to lf. usage: @@ -23,7 +23,7 @@ %macro mp_cleancsv(in=NOTPROVIDED,out=NOTPROVIDED,qchar='22'x); %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; %end; @@ -32,9 +32,9 @@ %if %index(&out,.) %then %let out="&out"; /** - * convert all cr and crlf within quotes to lf - * convert all other cr or lf to crlf - */ + * convert all cr and crlf within quotes to lf + * convert all other cr or lf to crlf + */ data _null_; infile &in recfm=n ; file &out recfm=n; diff --git a/base/mp_createconstraints.sas b/base/mp_createconstraints.sas index fcf17f9..1a84b19 100644 --- a/base/mp_createconstraints.sas +++ b/base/mp_createconstraints.sas @@ -13,7 +13,7 @@ constraint unq unique(tx_from, dd_type), constraint nnn not null(DD_SHORTDESC) ); - + %mp_getconstraints(lib=work,ds=example,outds=work.constraints) %mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES) %mp_createconstraints(inds=work.constraints,outds=created,execute=YES) @@ -48,7 +48,7 @@ data &outds; else type=constraint_type; create_statement=catx(" ","alter table",libref,".",table_name ,"add constraint",constraint_name,type,"("); - if last.constraint_name then + if last.constraint_name then create_statement=cats(create_statement,column_name,");"); else create_statement=cats(create_statement,column_name,","); if "&execute"="YES" then call execute(create_statement); diff --git a/base/mp_createwebservice.sas b/base/mp_createwebservice.sas index 3dce2ad..57aac8e 100644 --- a/base/mp_createwebservice.sas +++ b/base/mp_createwebservice.sas @@ -14,7 +14,7 @@ Usage: filename ft15f001 temp; parmcards4; %* fetch any data from frontend ; - %webout(FETCH) + %webout(FETCH) data example1 example2; set sashelp.class; run; diff --git a/base/mp_csv2ds.sas b/base/mp_csv2ds.sas index c18a9ed..3a7ba42 100644 --- a/base/mp_csv2ds.sas +++ b/base/mp_csv2ds.sas @@ -62,8 +62,8 @@ %local hasheader; %let hasheader=0; data _null_; if _N_ > 1 then do; - call symputx('hasheader',1,'l'); - stop; + call symputx('hasheader',1,'l'); + stop; end; infile &inref; input; @@ -131,7 +131,7 @@ run; /* import the CSV */ data &outds %if %upcase(&view)=YES %then %do; - /view=&outds + /view=&outds %end; ; infile &inref dsd firstobs=2; diff --git a/base/mp_deleteconstraints.sas b/base/mp_deleteconstraints.sas index d307929..e7e37d3 100644 --- a/base/mp_deleteconstraints.sas +++ b/base/mp_deleteconstraints.sas @@ -13,7 +13,7 @@ constraint unq unique(tx_from, dd_type), constraint nnn not null(DD_SHORTDESC) ); - + %mp_getconstraints(lib=work,ds=example,outds=work.constraints) %mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES) diff --git a/base/mp_dirlist.sas b/base/mp_dirlist.sas index 6d30ae6..42d590c 100644 --- a/base/mp_dirlist.sas +++ b/base/mp_dirlist.sas @@ -38,13 +38,13 @@ @returns outds contains the following variables: - - directory (containing folder) - - file_or_folder (file / folder) - - filepath (path/to/file.name) - - filename (just the file name) - - ext (.extension) - - msg (system message if any issues) - - OS SPECIFIC variables, if getattrs= is used. + - directory (containing folder) + - file_or_folder (file / folder) + - filepath (path/to/file.name) + - filename (just the file name) + - ext (.extension) + - msg (system message if any issues) + - OS SPECIFIC variables, if getattrs= is used. @version 9.2 @author Allan Bowe @@ -57,8 +57,11 @@ )/*/STORE SOURCE*/; %let getattrs=%upcase(&getattrs)XX; -data &outds (compress=no 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; +data &outds(compress=no + 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; rc = filename(fref, "&path"); %end; @@ -67,15 +70,15 @@ data &outds (compress=no keep=file_or_folder filepath filename ext msg directory rc=0; %end; if rc = 0 then do; - did = dopen(fref); - directory=dinfo(did,'Directory'); - if did=0 then do; - putlog "NOTE: This directory is empty - " directory; - msg=sysmsg(); - put _all_; - stop; - end; - rc = filename(fref); + did = dopen(fref); + directory=dinfo(did,'Directory'); + if did=0 then do; + putlog "NOTE: This directory is empty - " directory; + msg=sysmsg(); + put _all_; + stop; + end; + rc = filename(fref); end; else do; msg=sysmsg(); @@ -98,7 +101,7 @@ data &outds (compress=no keep=file_or_folder filepath filename ext msg directory if index(fmsg,'File is in use') or index(dmsg,'is not a directory') then file_or_folder='file'; - else if index(fmsg, 'Insufficient authorization') then file_or_folder='file'; + else if index(fmsg,'Insufficient authorization') then file_or_folder='file'; else if file_or_folder='' then file_or_folder='locked'; if file_or_folder='file' then do; diff --git a/base/mp_distinctfmtvalues.sas b/base/mp_distinctfmtvalues.sas index 33b1552..54cd22e 100644 --- a/base/mp_distinctfmtvalues.sas +++ b/base/mp_distinctfmtvalues.sas @@ -19,7 +19,7 @@ **/ %macro mp_distinctfmtvalues( - libds= + libds= ,var= ,outvar=formatted_value ,outds=work.mp_distinctfmtvalues @@ -34,7 +34,7 @@ create table &outds as select distinct %if &vtype=C & %trim(&fmt)=%str() %then %do; - &var + &var %end; %else %if &vtype=C %then %do; put(&var,&fmt) @@ -45,6 +45,6 @@ %else %do; put(&var,&fmt) %end; - as &outvar length=&varlen + as &outvar length=&varlen from &libds; %mend; \ No newline at end of file diff --git a/base/mp_dropmembers.sas b/base/mp_dropmembers.sas index f6bb11c..a62d352 100755 --- a/base/mp_dropmembers.sas +++ b/base/mp_dropmembers.sas @@ -22,7 +22,7 @@ **/ %macro mp_dropmembers( - list /* space separated list of datasets / views */ + list /* space separated list of datasets / views */ ,libref=WORK /* can only drop from a single library at a time */ )/*/STORE SOURCE*/; diff --git a/base/mp_ds2cards.sas b/base/mp_ds2cards.sas index b56e84a..ac553ef 100644 --- a/base/mp_ds2cards.sas +++ b/base/mp_ds2cards.sas @@ -10,20 +10,22 @@ , maxobs=5) TODO: - - labelling the dataset - - explicity setting a unix LF - - constraints / indexes etc + - labelling the dataset + - explicity setting a unix LF + - constraints / indexes etc - @param [in] base_ds= Should be two level - eg work.blah. This is the table that - is converted to a cards file. - @param [in] tgt_ds= Table that the generated cards file would create. Optional - - if omitted, will be same as BASE_DS. + @param [in] base_ds= Should be two level - eg work.blah. This is the table + that is converted to a cards file. + @param [in] tgt_ds= Table that the generated cards file would create. + Optional - if omitted, will be same as BASE_DS. @param [out] cards_file= Location in which to write the (.sas) cards file - @param [in] maxobs= to limit output to the first maxobs observations - @param [in] showlog= whether to show generated cards file in the SAS log (YES/NO) + @param [in] maxobs= to limit output to the first maxobs + observations + @param [in] showlog= whether to show generated cards file in the SAS log + (YES/NO) @param [in] outencoding= provide encoding value for file statement (eg utf-8) - @param [in] append= If NO then will rebuild the cards file if it already exists, - otherwise will append to it. Used by the mp_lib2cards.sas macro. + @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 @@ -41,8 +43,8 @@ %local i setds nvars; %if not %sysfunc(exist(&base_ds)) %then %do; - %put WARNING: &base_ds does not exist; - %return; + %put WARNING: &base_ds does not exist; + %return; %end; %if %index(&base_ds,.)=0 %then %let base_ds=WORK.&base_ds; @@ -64,10 +66,12 @@ select count(*) into: nvars from dictionary.columns %end; /* get indexes */ -proc sort data=sashelp.vindex - (where=(upcase(libname)="%scan(%upcase(&base_ds),1)" - and upcase(memname)="%scan(%upcase(&base_ds),2)")) - out=_data_; +proc sort + data=sashelp.vindex( + where=(upcase(libname)="%scan(%upcase(&base_ds),1)" + and upcase(memname)="%scan(%upcase(&base_ds),2)") + ) + out=_data_; by indxname indxpos; run; @@ -82,7 +86,7 @@ data _null_; idxcnt+1; nom=''; uni=''; - vars=name; + vars=name; end; else vars=catx(' ',vars,name); if last.indxname then do; @@ -110,8 +114,8 @@ proc sql ; reset outobs=max; create table datalines1 as - select name,type,length,varnum,format,label from dictionary.columns - where libname="%upcase(%scan(&base_ds,1))" + select name,type,length,varnum,format,label from dictionary.columns + where libname="%upcase(%scan(&base_ds,1))" and memname="%upcase(%scan(&base_ds,2))"; /** @@ -126,7 +130,7 @@ create table datalines1 as data datalines_2; format dataline $32000.; - set datalines1 (where=(upcase(name) not in + set datalines1 (where=(upcase(name) not in ('PROCESSED_DTTM','VALID_FROM_DTTM','VALID_TO_DTTM'))); if type='num' then dataline= cats('ifc(int(',name,')=',name,' @@ -140,9 +144,9 @@ proc sql noprint; select dataline into: datalines separated by ',' from datalines_2; %local - process_dttm_flg - valid_from_dttm_flg - valid_to_dttm_flg + process_dttm_flg + valid_from_dttm_flg + valid_to_dttm_flg ; %let process_dttm_flg = N; %let valid_from_dttm_flg = N; @@ -212,7 +216,7 @@ data _null_; put "input "; %do i = 1 %to &nvars.; %if(%length(&&input_stmt_&i..)) %then - put " &&input_stmt_&i.."; + put " &&input_stmt_&i.."; ; %end; put ";"; diff --git a/base/mp_ds2csv.sas b/base/mp_ds2csv.sas index b62adf8..30c95bb 100644 --- a/base/mp_ds2csv.sas +++ b/base/mp_ds2csv.sas @@ -19,8 +19,8 @@ )/*/STORE SOURCE*/; %if not %sysfunc(exist(&ds)) %then %do; - %put WARNING: &ds does not exist; - %return; + %put WARNING: &ds does not exist; + %return; %end; %if %index(&ds,.)=0 %then %let ds=WORK.&ds; @@ -36,22 +36,22 @@ /* first get headers */ data _null_; - file &outloc dlm=',' dsd &outencoding lrecl=32767; - length header $ 2000; - dsid=open("&ds.","i"); - num=attrn(dsid,"nvars"); - do i=1 to num; - header = trim(left(coalescec(varlabel(dsid,i),varname(dsid,i)))); - put header @; - end; - rc=close(dsid); + file &outloc dlm=',' dsd &outencoding lrecl=32767; + length header $ 2000; + dsid=open("&ds.","i"); + num=attrn(dsid,"nvars"); + do i=1 to num; + header = trim(left(coalescec(varlabel(dsid,i),varname(dsid,i)))); + put header @; + end; + rc=close(dsid); run; /* next, export data */ data _null_; - set &ds.; - file &outloc mod dlm=',' dsd &outencoding lrecl=32767; - put (_all_) (+0); + set &ds.; + file &outloc mod dlm=',' dsd &outencoding lrecl=32767; + put (_all_) (+0); run; diff --git a/base/mp_getconstraints.sas b/base/mp_getconstraints.sas index 49bf87a..6be4f9d 100644 --- a/base/mp_getconstraints.sas +++ b/base/mp_getconstraints.sas @@ -14,7 +14,7 @@ constraint unq unique(tx_from, dd_type), constraint nnn not null(DD_SHORTDESC) ); - + %mp_getconstraints(lib=work,ds=example,outds=work.constraints) @param lib= The target library (default=WORK) @@ -49,8 +49,8 @@ create table &outds as on a.TABLE_CATALOG=b.TABLE_CATALOG and a.TABLE_NAME=b.TABLE_NAME and a.constraint_name=b.constraint_name - where a.TABLE_CATALOG="&lib" - and b.TABLE_CATALOG="&lib" + where a.TABLE_CATALOG="&lib" + and b.TABLE_CATALOG="&lib" %if "&ds" ne "" %then %do; and a.TABLE_NAME="&ds" and b.TABLE_NAME="&ds" diff --git a/base/mp_getdbml.sas b/base/mp_getdbml.sas index 6ab059f..542f625 100644 --- a/base/mp_getdbml.sas +++ b/base/mp_getdbml.sas @@ -133,7 +133,9 @@ run; if notnull='yes' then notnul=' not null'; 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 put ' ' name typ '[' notnul ',' lab ']'; @@ -166,7 +168,7 @@ run; call symputx('constcheck',1); 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; curds="&curds"; @@ -176,7 +178,8 @@ 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); set &idxinfo (where=( libname="%scan(&curds,1,.)" @@ -187,7 +190,7 @@ run; file &outref mod; by idxusage indxname; 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 {'; length cols $5000; @@ -217,8 +220,8 @@ run; %end; /** - * now we need to figure out the relationships - */ + * now we need to figure out the relationships + */ /* sort alphabetically so we can have one set of unique cols per table */ proc sort data=&pkds nodupkey; @@ -226,7 +229,7 @@ proc sort data=&pkds nodupkey; run; data &pkds.1 (keep=curds col) - &pkds.2 (keep=curds cols); + &pkds.2 (keep=curds cols); set &pkds; by curds const; length retconst $39 cols $5000; @@ -261,7 +264,11 @@ run; line='Ref: "'!!"&curds" !!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; run; @@ -282,7 +289,9 @@ run; create table &pkds.5b as select curds,count(*) as cnt 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 */ group by 1; create table &pkds.6 as diff --git a/base/mp_getddl.sas b/base/mp_getddl.sas index fb04504..3547c64 100644 --- a/base/mp_getddl.sas +++ b/base/mp_getddl.sas @@ -27,7 +27,7 @@ @param schema= Choose a preferred schema name (default is to use actual schema ,else libref) @param applydttm= for non SAS DDL, choose if columns are created with native - datetime2 format or regular decimal type + datetime2 format or regular decimal type @version 9.3 @author Allan Bowe **/ @@ -86,7 +86,9 @@ create table _data_ as %global constraints_used; data _null_; 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; by constraint_type constraint_name; retain constraints_used; @@ -161,10 +163,19 @@ run; put ');'; 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_; *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; by idxusage indxname; /* ds=cats(libname,'.',memname); */ @@ -228,10 +239,19 @@ run; /* Extra step for data constraints */ %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_; *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; by idxusage indxname; *ds=cats(libname,'.',memname); @@ -320,15 +340,24 @@ run; put ');'; 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_; *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; by idxusage indxname; /* ds=cats(libname,'.',memname); */ if first.indxname then do; - put 'CREATE UNIQUE INDEX "' indxname +(-1) '" ' "ON &schema..&curds (" ; + put 'CREATE UNIQUE INDEX "' indxname +(-1) '" ' "ON &schema..&curds("; put ' "' name +(-1) '"' ; end; else put ' ,"' name +(-1) '"'; diff --git a/base/mp_getmaxvarlengths.sas b/base/mp_getmaxvarlengths.sas index 21b6c02..1bde42f 100755 --- a/base/mp_getmaxvarlengths.sas +++ b/base/mp_getmaxvarlengths.sas @@ -1,17 +1,18 @@ /** @file mp_getmaxvarlengths.sas @brief Scans a dataset to find the max length of the variable values - @details + @details This macro will scan a base dataset and produce an output dataset with two columns: - NAME Name of the base dataset column - MAXLEN Maximum length of the data contained therein. - Character fields may be allocated very large widths (eg 32000) of which the maximum - value is likely to be much narrower. This macro was designed to enable a HTML - table to be appropriately sized however this could be used as part of a data - audit to ensure we aren't over-sizing our tables in relation to the data therein. + Character fields may be allocated very large widths (eg 32000) of which the + maximum value is likely to be much narrower. This macro was designed to + enable a HTML table to be appropriately sized however this could be used as + 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. Usage: @@ -33,7 +34,7 @@ %macro mp_getmaxvarlengths( libds /* libref.dataset to analyse */ - ,outds=work.mp_getmaxvarlengths /* name of output dataset to create */ + ,outds=work.mp_getmaxvarlengths /* name of output dataset to create */ )/*/STORE SOURCE*/; %local vars x var fmt; diff --git a/base/mp_jsonout.sas b/base/mp_jsonout.sas index 80acee9..1ac804d 100644 --- a/base/mp_jsonout.sas +++ b/base/mp_jsonout.sas @@ -4,8 +4,8 @@ @details PROC JSON is faster but will produce errs like the ones below if special chars are encountered. - >An object or array close is not valid at this point in the JSON text. - >Date value out of range + >An object or array close is not valid at this point in the JSON text. + >Date value out of range If this happens, try running with ENGINE=DATASTEP. @@ -13,7 +13,7 @@ filename tmp temp; data class; set sashelp.class;run; - + %mp_jsonout(OBJ,class,jref=tmp) data _null_; @@ -22,7 +22,7 @@ run; If you are building web apps with SAS then you are strongly encouraged to use - the mX_createwebservice macros in combination with the + the mX_createwebservice macros in combination with the [sasjs adapter](https://github.com/sasjs/adapter). For more information see https://sasjs.io @@ -38,11 +38,11 @@ @param fmt= Whether to keep or strip formats from the table @param engine= Which engine to use to send the JSON, options are: * PROCJSON (default) - * DATASTEP + * DATASTEP @param dbg= DEPRECATED - was used to conditionally add PRETTY to proc json but this can cause line truncation in large files. - + @version 9.2 @author Allan Bowe @@ -66,7 +66,7 @@ %if &engine=PROCJSON %then %do; data;run;%let tempds=&syslast; proc sql;drop table &tempds; - data &tempds /view=&tempds;set &ds; + data &tempds /view=&tempds;set &ds; %if &fmt=N %then format _numeric_ best32.;; proc json out=&jref pretty %if &action=ARR %then nokeys ; @@ -81,13 +81,14 @@ %put &sysmacroname: &ds NOT FOUND!!!; %return; %end; - data _null_;file &jref mod ; + data _null_;file &jref mod ; 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_; by varnum; - data _null_; + data _null_; set _last_ end=last; call symputx(cats('name',_n_),name,'l'); call symputx(cats('type',_n_),type,'l'); @@ -121,8 +122,9 @@ )))))!!'"'; %end; %end; - run; - /* write to temp loc to avoid _webout truncation - https://support.sas.com/kb/49/325.html */ + run; + /* write to temp loc to avoid _webout truncation + - https://support.sas.com/kb/49/325.html */ filename _sjs temp lrecl=131068 encoding='utf-8'; data _null_; file _sjs lrecl=131068 encoding='utf-8' mod; set &tempds; @@ -131,7 +133,7 @@ %do i=1 %to &cols; %if &i>1 %then "," ; %if &action=OBJ %then """&&name&i"":" ; - &&name&i + &&name&i %end; %if &action=ARR %then "]" ; %else "}" ; ; proc sql; diff --git a/base/mp_prevobs.sas b/base/mp_prevobs.sas index 7bcdf94..ad49786 100644 --- a/base/mp_prevobs.sas +++ b/base/mp_prevobs.sas @@ -16,13 +16,13 @@ %mp_prevobs(INIT,history=2) if _n_ =10 then do; %* fetch previous but 1 record; - %mp_prevobs(FETCH,-2) - put _n_= name= age= calc_var=; + %mp_prevobs(FETCH,-2) + put _n_= name= age= calc_var=; %* fetch previous record; - %mp_prevobs(FETCH,-1) - put _n_= name= age= calc_var=; + %mp_prevobs(FETCH,-1) + put _n_= name= age= calc_var=; %* reinstate current record ; - %mp_prevobs(FETCH,0) + %mp_prevobs(FETCH,0) put _n_= name= age= calc_var=; end; run; @@ -35,11 +35,11 @@ https://www.lexjansen.com/pharmasug/2008/cc/CC08.pdf @param action Either FETCH a current or previous record, or INITialise. - @param record The relative (to current) position of the previous observation - to return. + @param record The relative (to current) position of the previous observation + to return. @param history= The number of records to retain in the hash table. Default=5 @param prefix= the prefix to give to the variables used to store the hash name - and index. Default=mp_prevobs + and index. Default=mp_prevobs @version 9.2 @author Allan Bowe @@ -53,33 +53,33 @@ %let record=%eval((&record+0) * -1); %if &action=INIT %then %do; - - if _n_ eq 1 then do; - attrib &prefix._VAR length=$64; + + if _n_ eq 1 then do; + attrib &prefix._VAR length=$64; dcl hash &prefix._HASH(ordered:'Y'); &prefix._KEY=0; - &prefix._HASH.defineKey("&prefix._KEY"); - do while(1); - call vnext(&prefix._VAR); + &prefix._HASH.defineKey("&prefix._KEY"); + do while(1); + call vnext(&prefix._VAR); if &prefix._VAR='' then leave; - if &prefix._VAR eq "&prefix._VAR" then continue; - else if &prefix._VAR eq "&prefix._KEY" then continue; + if &prefix._VAR eq "&prefix._VAR" then continue; + else if &prefix._VAR eq "&prefix._KEY" then continue; &prefix._HASH.defineData(&prefix._VAR); - end; - &prefix._HASH.defineDone(); + end; + &prefix._HASH.defineDone(); end; /* this part has to happen before FETCHing */ &prefix._KEY+1; &prefix._rc=&prefix._HASH.add(); if &prefix._rc then putlog 'adding' &prefix._rc=; %if &history>0 %then %do; - if &prefix._key>&history+1 then + if &prefix._key>&history+1 then &prefix._HASH.remove(key: &prefix._KEY - &history - 1); if &prefix._rc then putlog 'removing' &prefix._rc=; %end; %end; %else %if &action=FETCH %then %do; - if &record > &prefix._key then putlog "Not enough records in &Prefix._hash yet"; + if &record>&prefix._key then putlog "Not enough records in &Prefix._hash yet"; else &prefix._rc=&prefix._HASH.find(key: &prefix._KEY - &record); if &prefix._rc then putlog &prefix._rc= " when fetching " &prefix._KEY= "with record &record and " _n_=; diff --git a/base/mp_recursivejoin.sas b/base/mp_recursivejoin.sas index 0a1579e..30f20e9 100644 --- a/base/mp_recursivejoin.sas +++ b/base/mp_recursivejoin.sas @@ -32,9 +32,9 @@ @returns outds contains the following variables: - - level (0 = top level) - - &parentvar - - &childvar (null if none found) + - level (0 = top level) + - &parentvar + - &childvar (null if none found) @version 9.2 @author Allan Bowe diff --git a/base/mp_runddl.sas b/base/mp_runddl.sas index 619cb63..032962f 100644 --- a/base/mp_runddl.sas +++ b/base/mp_runddl.sas @@ -8,14 +8,14 @@ rootlib |-- LIBREF1 - | |__ mytable.ddl - | |__ someothertable.ddl + | |__ mytable.ddl + | |__ someothertable.ddl |-- LIBREF2 - | |__ table1.ddl - | |__ table2.ddl + | |__ table1.ddl + | |__ table2.ddl |-- LIBREF3 - |__ table3.ddl - |__ table4.ddl + |__ table3.ddl + |__ table4.ddl Only files with the .ddl suffix are executed. The parent folder name is used as the libref. diff --git a/base/mp_searchcols.sas b/base/mp_searchcols.sas index b41b18a..fb25e48 100644 --- a/base/mp_searchcols.sas +++ b/base/mp_searchcols.sas @@ -32,7 +32,7 @@ create table _data_ as where upcase(libname) in ("IMPOSSIBLE", %local x; %do x=1 %to %sysfunc(countw(&libs)); - "%upcase(%scan(&libs,&x))" + "%upcase(%scan(&libs,&x))" %end; ) %end; diff --git a/base/mp_searchdata.sas b/base/mp_searchdata.sas index c8bdee3..0d192e4 100644 --- a/base/mp_searchdata.sas +++ b/base/mp_searchdata.sas @@ -3,7 +3,7 @@ @brief Searches all data in a library @details Scans an entire library and creates a copy of any table - containing a specific string OR numeric value. Only + containing a specific string OR numeric value. Only matching records are written out. If both a string and numval are provided, the string will take precedence. @@ -20,9 +20,10 @@ @param ds= the dataset to search (leave blank to search entire library) @param string= the string value to search @param numval= the numeric value to search (must be exact) - @param outloc= the directory in which to create the output datasets with 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 outloc= the directory in which to create the output datasets with + 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 filter_text= add a (valid) filter clause to further filter the results

SAS Macros

@@ -36,7 +37,7 @@ **/ %macro mp_searchdata(lib=sashelp - ,ds= + ,ds= ,string= /* the query will use a contains (?) operator */ ,numval= /* numeric must match exactly */ ,outloc=%sysfunc(pathname(work))/mpsearch @@ -44,7 +45,8 @@ ,filter_text=%str(1=1) )/*/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.); %if &syscc ge 4 %then %do; @@ -61,14 +63,14 @@ libname mpsearch "&outloc"; /* get the list of tables in the library */ proc sql noprint; select distinct memname into: table_list separated by ' ' - from dictionary.tables + from dictionary.tables where upcase(libname)="%upcase(&lib)" %if &ds ne %then %do; and upcase(memname)=%upcase("&ds") %end; ; /* check that we have something to check */ -proc sql +proc sql %if &outobs>-1 %then %do; outobs=&outobs %end; @@ -85,7 +87,7 @@ proc sql %let check_tm=%sysfunc(datetime()); /* build sql statement */ create table mpsearch.&table as select * from &lib..&table - where %unquote(&filter_text) and + where %unquote(&filter_text) and (0 /* loop through columns */ %do colnum=1 %to %sysfunc(countw(&vars,%str( ))); @@ -101,7 +103,8 @@ proc sql %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; %put %str(WAR)NING: SQLRC=&sqlrc when processing &table; %return; diff --git a/base/mp_setkeyvalue.sas b/base/mp_setkeyvalue.sas index b01a5e3..ec8021c 100644 --- a/base/mp_setkeyvalue.sas +++ b/base/mp_setkeyvalue.sas @@ -12,7 +12,7 @@ @param key Provide a key on which to perform the lookup @param value Provide a value @param type= either C or N will populate valc and valn respectively. C is - default. + default. @param libds= define the target table to hold the parameters @version 9.2 diff --git a/base/mp_stprequests.sas b/base/mp_stprequests.sas index a39ce30..3e001a8 100644 --- a/base/mp_stprequests.sas +++ b/base/mp_stprequests.sas @@ -1,12 +1,13 @@ /** @file @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): proc sql; create table &libds( - request_dttm num not null format=datetime. + request_dttm num not null format=datetime. ,status_cd char(4) not null ,_metaperson varchar(100) not null ,_program varchar(500) diff --git a/base/mp_streamfile.sas b/base/mp_streamfile.sas index 562af13..9484d1c 100644 --- a/base/mp_streamfile.sas +++ b/base/mp_streamfile.sas @@ -6,7 +6,8 @@ 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; %mp_streamfile(contenttype=csv,inloc=/some/where.txt,outname=myfile.txt) @@ -65,13 +66,15 @@ %else %if &contentype=XLSX %then %do; %if &platform=SASMETA %then %do; 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"); run; %end; %else %if &platform=SASVIYA %then %do; 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"; %end; %end; @@ -113,10 +116,10 @@ %end; %if &inref ne 0 %then %do; - %mp_binarycopy(inref=&inref,outref=_webout) + %mp_binarycopy(inref=&inref,outref=_webout) %end; %else %do; - %mp_binarycopy(inloc="&inloc",outref=_webout) + %mp_binarycopy(inloc="&inloc",outref=_webout) %end; %mend; \ No newline at end of file diff --git a/base/mp_testjob.sas b/base/mp_testjob.sas index 085ef86..665caaa 100644 --- a/base/mp_testjob.sas +++ b/base/mp_testjob.sas @@ -5,7 +5,7 @@ testing of arbitrary jobs. %mp_testjob( - duration=60*5 + duration=60*5 ) @param [in] duration= the time in seconds which the job should run for. Actual diff --git a/base/mp_testwritespeedlibrary.sas b/base/mp_testwritespeedlibrary.sas index b2f0742..ce7c0f2 100644 --- a/base/mp_testwritespeedlibrary.sas +++ b/base/mp_testwritespeedlibrary.sas @@ -1,10 +1,10 @@ /** @file mp_testwritespeedlibrary.sas @brief Tests the write speed of a new table in a SAS library - @details Will create a new table of a certain size in an + @details Will create a new table of a certain size in an existing SAS library. The table will have one column, and will be subsequently deleted. - + %mp_testwritespeedlibrary( lib=work ,size=0.5 diff --git a/base/mp_tree.sas b/base/mp_tree.sas index 4ec197e..1df0fbb 100644 --- a/base/mp_tree.sas +++ b/base/mp_tree.sas @@ -8,8 +8,11 @@ Credits: - * Roger Deangelis, 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 + Roger Deangelis: +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) @@ -17,13 +20,13 @@ @returns outds contains the following variables: - - `dir`: a flag (1/0) to say whether it is a directory or not. This is not - reliable - folders that you do not have permission to open will be flagged - as directories. - - `ext`: file extension - - `filename`: file name - - `dirname`: directory name - - `fullpath`: directory + file name + - `dir`: a flag (1/0) to say whether it is a directory or not. This is not + reliable - folders that you do not have permission to open will be flagged + as directories. + - `ext`: file extension + - `filename`: file name + - `dirname`: directory name + - `fullpath`: directory + file name @version 9.2 **/ diff --git a/meta/mm_assigndirectlib.sas b/meta/mm_assigndirectlib.sas index fd77af9..0f40f10 100755 --- a/meta/mm_assigndirectlib.sas +++ b/meta/mm_assigndirectlib.sas @@ -33,7 +33,7 @@ **/ %macro mm_assigndirectlib( - libref /* libref to assign from metadata */ + libref /* libref to assign from metadata */ ,open_passthrough= /* provide an alias to produce the CONNECT TO statement for the relevant external database */ @@ -107,7 +107,7 @@ run; run; %if %sysevalf(&sysver<9.4) %then %do; - libname &libref &filepath; + libname &libref &filepath; %end; %else %do; /* apply the new filelocks option to cater for temporary locks */ @@ -117,7 +117,8 @@ run; %end; %else %if &engine=REMOTE %then %do; 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; rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon); @@ -129,8 +130,9 @@ run; rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue); rc = metadata_getattr(uriProp , "PropertyName",PropertyName); rc = metadata_getattr(uriProp , "Delimiter",Delimiter); - properties = trim(properties) !! " " !! trim(PropertyName) !! trim(Delimiter) !! trim(PropertyValue); - output; + properties = trim(properties) !! " " !! trim(PropertyName) + !! trim(Delimiter) !! trim(PropertyValue); + output; k+1; rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp); end; @@ -165,13 +167,14 @@ run; rc=metadata_getnasn(connx_uri,'Properties',i,conprop_uri); rc2=metadata_getattr(conprop_uri,'Name',value); if value='Connection.OLE.Property.DATASOURCE.Name.xmlKey.txt' then do; - rc3=metadata_getattr(conprop_uri,'DefaultValue',datasource); + rc3=metadata_getattr(conprop_uri,'DefaultValue',datasource); end; 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; - else if value='Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt' then do; - rc5=metadata_getattr(conprop_uri,'DefaultValue',properties); + else if value='Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt' then + do; + rc5=metadata_getattr(conprop_uri,'DefaultValue',properties); end; end; &mD.putlog 'NOTE- dsn/provider/properties: ' / @@ -194,8 +197,8 @@ run; /* need additional properties to make this work */ properties=('Integrated Security'=SSPI 'Persist Security Info'=True - %sysfunc(compress(%str(&SQL_properties),%str(()))) - ) + %sysfunc(compress(%str(&SQL_properties),%str(()))) + ) DATASOURCE=&sql_dsn PROMPT=NO PROVIDER=&sql_provider SCHEMA=&sql_schema CONNECTION = GLOBAL); %end; @@ -203,9 +206,9 @@ run; LIBNAME &libref OLEDB PROPERTIES=&sql_properties DATASOURCE=&sql_dsn PROVIDER=&sql_provider SCHEMA=&sql_schema %if %length(&sql_domain)>0 %then %do; - authdomain="&sql_domain" + authdomain="&sql_domain" %end; - connection=shared; + connection=shared; %end; %end; %else %if &engine=ODBC %then %do; @@ -222,8 +225,8 @@ run; rc2=metadata_getnasn(connx_uri,'Properties',i,conprop_uri); rc3=metadata_getattr(conprop_uri,'Name',value); if value='Connection.ODBC.Property.DATASRC.Name.xmlKey.txt' then do; - rc4=metadata_getattr(conprop_uri,'DefaultValue',datasource); - rc2=-1; + rc4=metadata_getattr(conprop_uri,'DefaultValue',datasource); + rc2=-1; end; end; /* get SCHEMA */ @@ -280,7 +283,7 @@ run; /* get PRESERVE_TAB_NAMES value */ /* be careful with PRESERVE_TAB_NAMES=YES - it will mean your table will - become case sensitive!! */ + become case sensitive!! */ prop='Library.DBMS.Property.PreserveTabNames.Name.xmlKey.txt'; rc=metadata_getprop("&liburi",prop,preserve_tab_names,""); if preserve_tab_names^='' then preserve_tab_names= @@ -357,7 +360,8 @@ run; call symputx('authdomain',authdomain,'l'); /* 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'); /* schema */ @@ -366,27 +370,30 @@ run; call symputx('schema',schema,'l'); run; %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-; libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain; %end; %else %if &engine=SQLSVR %then %do; %put NOTE: Obtaining &engine library details; 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_); - + rc=metadata_getnasn("&liburi",'DefaultLogin',1,assocuri1); rc=metadata_getattr(assocuri1,"UserID",userid); rc=metadata_getattr(assocuri1,"Password",passwd); call symputx('user',userid,'l'); call symputx('pass',passwd,'l'); - + /* path */ 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'); - + /* schema */ rc=metadata_getnasn("&liburi",'UsingPackages',1,assocuri3); rc=metadata_getattr(assocuri3,'SchemaName',schema); @@ -394,17 +401,19 @@ run; run; %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-; - libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass" ; + libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass"; %end; %else %if &engine=TERADATA %then %do; %put NOTE: Obtaining &engine library details; 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_); - + /* get auth domain */ rc=metadata_getnasn("&liburi",'LibraryConnection',1,assocuri1); rc=metadata_getnasn(assocuri1,'Domain',1,assocuri2); @@ -421,9 +430,10 @@ run; /* path */ 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'); - + /* schema */ rc=metadata_getnasn("&liburi",'UsingPackages',1,assocuri3); rc=metadata_getattr(assocuri3,'SchemaName',schema); @@ -431,7 +441,8 @@ run; run; %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-; libname &libref TERADATA server=&path schema=&schema authdomain=&authdomain; diff --git a/meta/mm_assignlib.sas b/meta/mm_assignlib.sas index 047e798..0d81f80 100755 --- a/meta/mm_assignlib.sas +++ b/meta/mm_assignlib.sas @@ -14,7 +14,8 @@ @li mp_abort.sas @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 @@ -24,7 +25,7 @@ **/ %macro mm_assignlib( - libref + libref ,mAbort=HARD )/*/STORE SOURCE*/; diff --git a/meta/mm_createapplication.sas b/meta/mm_createapplication.sas index db9f0f8..62c01ad 100644 --- a/meta/mm_createapplication.sas +++ b/meta/mm_createapplication.sas @@ -13,7 +13,9 @@ ,params= name1=value1 name2=value2 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).

SAS Macros

@li mp_abort.sas @@ -60,8 +62,8 @@ %mf_verifymacvars(tree name) /** - * check tree exists - */ + * check tree exists + */ data _null_; length type uri $256; @@ -77,8 +79,8 @@ run; ) /** - * Check object does not exist already - */ + * Check object does not exist already + */ data _null_; length type uri $256; rc=metadata_pathobj("","&tree/&name","Application",type,uri); @@ -94,8 +96,8 @@ run; /** - * Now we can create the application - */ + * Now we can create the application + */ filename &frefin temp; /* write header XML */ diff --git a/meta/mm_createdocument.sas b/meta/mm_createdocument.sas index d7d495e..c1b7d79 100644 --- a/meta/mm_createdocument.sas +++ b/meta/mm_createdocument.sas @@ -50,8 +50,8 @@ %mf_verifymacvars(tree name) /** - * check tree exists - */ + * check tree exists + */ data _null_; length type uri $256; @@ -67,8 +67,8 @@ run; ) /** - * Check object does not exist already - */ + * Check object does not exist already + */ data _null_; length type uri $256; rc=metadata_pathobj("","&tree/&name","Note",type,uri); @@ -83,8 +83,8 @@ run; %end; /** - * Now we can create the document - */ + * Now we can create the document + */ filename &frefin temp; /* write header XML */ diff --git a/meta/mm_createfolder.sas b/meta/mm_createfolder.sas index 5e5f190..47b1785 100644 --- a/meta/mm_createfolder.sas +++ b/meta/mm_createfolder.sas @@ -55,7 +55,7 @@ data _null_; * must have a starting slash ; 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; end; @@ -75,8 +75,8 @@ data _null_; * check that root folder exists ; root=cats('/',scan(folderpath,1,'/'),"(Folder)"); if metadata_pathobj('',root,"",objType,parentId)<1 then do; - put "%str(ERR)OR: " root " does not exist!"; - stop; + put "%str(ERR)OR: " root " does not exist!"; + stop; end; * check that parent folder exists ; @@ -86,21 +86,21 @@ data _null_; if rc<1 then do; putlog 'The following folders will be created:'; /* folder does not exist - so start from top and work down */ - length newpath $1000; - paths=0; - do x=2 to countw(folderpath,'/'); - newpath=''; - do i=1 to x; - newpath=cats(newpath,'/',scan(folderpath,i,'/')); - end; - rc=metadata_pathobj('',cats(newpath,"(Folder)"),"",objType,parentId); - if rc<1 then do; - paths+1; - call symputx(cats('path',paths),newpath); - putlog newpath; - end; - call symputx('paths',paths); - end; + length newpath $1000; + paths=0; + do x=2 to countw(folderpath,'/'); + newpath=''; + do i=1 to x; + newpath=cats(newpath,'/',scan(folderpath,i,'/')); + end; + rc=metadata_pathobj('',cats(newpath,"(Folder)"),"",objType,parentId); + if rc<1 then do; + paths+1; + call symputx(cats('path',paths),newpath); + putlog newpath; + end; + call symputx('paths',paths); + end; end; else putlog "parent " parent " exists"; @@ -115,7 +115,7 @@ run; %if &paths>0 %then %do x=1 %to &paths; %put executing recursive call for &&path&x; - %mm_createfolder(path=&&path&x) + %mm_createfolder(path=&&path&x) %end; %else %do; filename __newdir temp; @@ -123,9 +123,10 @@ run; %local inmeta; %put creating: &path; %let inmeta=$METAREPOSITORY - - - SAS268435456; + + SAS268435456 + ; proc metadata in="&inmeta" out=__newdir verbose; run ; diff --git a/meta/mm_createlibrary.sas b/meta/mm_createlibrary.sas index 91c45c6..8de4c0a 100644 --- a/meta/mm_createlibrary.sas +++ b/meta/mm_createlibrary.sas @@ -56,7 +56,7 @@ **/ %macro mm_createlibrary( - libname=My New Library + libname=My New Library ,libref=mynewlib ,libdesc=Created automatically using the mm_createlibrary macro ,engine=BASE @@ -78,8 +78,8 @@ %let libref=%upcase(&libref); /** - * Check Library does not exist already with this libname - */ + * Check Library does not exist already with this libname + */ data _null_; length type uri $256; rc=metadata_resolve("omsobj:SASLibrary?@Name='&libname'",type,uri); @@ -93,8 +93,8 @@ run; %end; /** - * Check Library does not exist already with this libref - */ + * Check Library does not exist already with this libref + */ data _null_; length type uri $256; rc=metadata_resolve("omsobj:SASLibrary?@Libref='&libref'",type,uri); @@ -109,13 +109,13 @@ run; /** - * Attempt to create tree - */ + * Attempt to create tree + */ %mm_createfolder(path=&tree) /** - * check tree exists - */ + * check tree exists + */ data _null_; length type uri $256; rc=metadata_pathobj("","&tree","Folder",type,uri); @@ -128,8 +128,8 @@ run; %end; /** - * Create filerefs for proc metadata call - */ + * Create filerefs for proc metadata call + */ filename &frefin temp; filename &frefout temp; @@ -140,8 +140,8 @@ filename &frefout temp; /** - * Check that the ServerContext exists - */ + * Check that the ServerContext exists + */ data _null_; length type uri $256; rc=metadata_resolve("omsobj:ServerContext?@Name='&ServerContext'",type,uri); @@ -155,8 +155,8 @@ filename &frefout temp; %end; /** - * Get prototype info - */ + * Get prototype info + */ data _null_; length type uri str $256; str="omsobj:Prototype?@Name='Library.SAS.Prototype.Name.xmlKey.txt'"; @@ -166,21 +166,21 @@ filename &frefout temp; putlog (_all_)(=); run; %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; %end; /** - * Check that Physical location exists - */ + * Check that Physical location exists + */ %if %sysfunc(fileexist(&directory))=0 %then %do; %put %str(ERR)OR: Physical directory (&directory) does not appear to exist!; %return; %end; /** - * Check that Directory Object exists in metadata - */ + * Check that Directory Object exists in metadata + */ data _null_; length type uri $256; rc=metadata_resolve("omsobj:Directory?@DirectoryRole='LibraryPath'" @@ -228,16 +228,16 @@ filename &frefout temp; %end; /** - * check SAS version - */ + * check SAS version + */ %if %sysevalf(&sysver lt 9.3) %then %do; %put WARNING: Version 9.3 or later required; %return; %end; /** - * Prepare the XML and create the library - */ + * Prepare the XML and create the library + */ data _null_; file &frefin; treeuri=quote(symget('treeuri')); @@ -311,8 +311,8 @@ filename &frefout temp; /** - * Wrap up - */ + * Wrap up + */ %if &mdebug ne 1 %then %do; filename &frefin clear; filename &frefout clear; diff --git a/meta/mm_createstp.sas b/meta/mm_createstp.sas index 54ae33a..cdbdeb3 100755 --- a/meta/mm_createstp.sas +++ b/meta/mm_createstp.sas @@ -72,10 +72,10 @@ foundation repo then select a different one here @returns outds dataset containing the following columns: - - stpuri - - prompturi - - fileuri - - texturi + - stpuri + - prompturi + - fileuri + - texturi @version 9.2 @author Allan Bowe @@ -83,7 +83,7 @@ **/ %macro mm_createstp( - stpname=Macro People STP + stpname=Macro People STP ,stpdesc=This stp was created automatically by the mm_createstp macro ,filename=mm_createstp.sas ,directory=SASEnvironment/SASCode @@ -109,8 +109,8 @@ %mp_dropmembers(%scan(&outds,2,.)) /** - * check tree exists - */ + * check tree exists + */ data _null_; length type uri $256; rc=metadata_pathobj("","&tree","Folder",type,uri); @@ -123,8 +123,8 @@ run; %end; /** - * Check STP does not exist already - */ + * Check STP does not exist already + */ %local cmtype; data _null_; length type uri $256; @@ -138,8 +138,8 @@ run; %end; /** - * Check that the physical file exists - */ + * Check that the physical file exists + */ %if %sysfunc(fileexist(&directory/&filename)) ne 1 %then %do; %put WARNING: FILE *&directory/&filename* NOT FOUND!; %return; @@ -210,7 +210,8 @@ run; rc3=METADATA_SETATTR(prompturi, 'GroupType','2'); rc4=METADATA_SETATTR(prompturi, 'Name','Parameters'); rc5=METADATA_SETATTR(prompturi, 'PublicType','Embedded:PromptGroup'); - GroupInfo=""; rc6 = METADATA_SETATTR(prompturi, 'GroupInfo',groupinfo); @@ -315,8 +316,8 @@ run; %end; /** - * First, create a Hello World type 2 stored process - */ + * First, create a Hello World type 2 stored process + */ filename &frefin temp; data _null_; file &frefin; @@ -371,8 +372,8 @@ run; %end; /** - * Next, add the source code - */ + * Next, add the source code + */ %mm_updatestpsourcecode(stp=&tree/&stpname ,stpcode="&directory/&filename" ,frefin=&frefin. diff --git a/meta/mm_createwebservice.sas b/meta/mm_createwebservice.sas index cb19750..751f364 100644 --- a/meta/mm_createwebservice.sas +++ b/meta/mm_createwebservice.sas @@ -22,7 +22,7 @@ Usage: %webout(OBJ,example2) * Object format, easier to work with ; %webout(CLOSE) ;;;; - %mm_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001,replace=YES) + %mm_createwebservice(path=/Public/app/common,name=appInit)

SAS Macros

@li mm_createstp.sas @@ -76,10 +76,10 @@ Usage: %let path=%substr(&path,1,%length(&path)-1); /** - * Add webout macro - * These put statements are auto generated - to change the macro, change the - * source (mm_webout) and run `build.py` - */ + * Add webout macro + * These put statements are auto generated - to change the macro, change the + * source (mm_webout) and run `build.py` + */ filename sasjs temp; data _null_; file sasjs lrecl=3000 ; @@ -119,7 +119,8 @@ data _null_; put ' %end; '; put ' data _null_;file &jref mod ; '; 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 ' by varnum; '; put ' '; @@ -158,7 +159,8 @@ data _null_; put ' %end; '; put ' %end; '; 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 ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; '; put ' set &tempds; '; diff --git a/meta/mm_deletedocument.sas b/meta/mm_deletedocument.sas index 90ce77c..0abafae 100644 --- a/meta/mm_deletedocument.sas +++ b/meta/mm_deletedocument.sas @@ -18,12 +18,12 @@ **/ %macro mm_deletedocument( - target= + target= )/*/STORE SOURCE*/; /** - * Check document exist - */ + * Check document exist + */ %local type; data _null_; length type uri $256; @@ -39,10 +39,10 @@ run; filename __in temp lrecl=10000; filename __out temp lrecl=10000; data _null_ ; - file __in ; - put ""; - put "SAS268436480"; - put ""; + file __in ; + put ""; + put "SAS268436480"; + put ""; run ; proc metadata in=__in out=__out verbose;run; @@ -53,8 +53,8 @@ filename __in clear; filename __out clear; /** - * Check deletion - */ + * Check deletion + */ %local isgone; data _null_; length type uri $256; diff --git a/meta/mm_deletestp.sas b/meta/mm_deletestp.sas index fe4ea2b..6fd41dd 100644 --- a/meta/mm_deletestp.sas +++ b/meta/mm_deletestp.sas @@ -17,12 +17,12 @@ **/ %macro mm_deletestp( - target= + target= )/*/STORE SOURCE*/; /** - * Check STP does exist - */ + * Check STP does exist + */ %local cmtype; data _null_; length type uri $256; @@ -38,10 +38,10 @@ run; filename __in temp lrecl=10000; filename __out temp lrecl=10000; data _null_ ; - file __in ; - put ""; - put "SAS268436480"; - put ""; + file __in ; + put ""; + put "SAS268436480"; + put ""; run ; proc metadata in=__in out=__out verbose;run; @@ -52,8 +52,8 @@ filename __in clear; filename __out clear; /** - * Check deletion - */ + * Check deletion + */ %local isgone; data _null_; length type uri $256; diff --git a/meta/mm_getauthinfo.sas b/meta/mm_getauthinfo.sas index 6a59380..f2aea00 100644 --- a/meta/mm_getauthinfo.sas +++ b/meta/mm_getauthinfo.sas @@ -21,8 +21,8 @@ )/*/STORE SOURCE*/; %if %length(&outds)>30 %then %do; - %put %str(ERR)OR: Temp tables are created with the &outds prefix, which therefore - needs to be 30 characters or less; + %put %str(ERR)OR: Temp tables are created with the &outds prefix, which + therefore needs to be 30 characters or less; %return; %end; %if %index(&outds,'.')>0 %then %do; @@ -58,11 +58,11 @@ data _null_; put str; if last then do; /* collate attributes */ - str=cats("data &outds._logat; set &outds.da1-&outds.da",_n_,";run;"); - put str; + str=cats("data &outds._logat; set &outds.da1-&outds.da",_n_,";run;"); + put str; /* collate associations */ - str=cats("data &outds._logas; set &outds.a1-&outds.a",_n_,";run;"); - put str; + str=cats("data &outds._logas; set &outds.a1-&outds.a",_n_,";run;"); + put str; /* tidy up */ str=cats("proc delete data=&outds.da1-&outds.da",_n_,";run;"); put str; diff --git a/meta/mm_getcols.sas b/meta/mm_getcols.sas index 107eb81..a647bee 100644 --- a/meta/mm_getcols.sas +++ b/meta/mm_getcols.sas @@ -21,7 +21,7 @@ **/ %macro mm_getcols( - tableuri= + tableuri= ,outds=work.mm_getcols )/*/STORE SOURCE*/; diff --git a/meta/mm_getdirectories.sas b/meta/mm_getdirectories.sas index 5d220b9..7fab045 100755 --- a/meta/mm_getdirectories.sas +++ b/meta/mm_getdirectories.sas @@ -10,9 +10,9 @@ @param mDebug= set to 1 to show debug messages in the log @returns outds dataset containing the following columns: - - directoryuri - - groupname - - groupdesc + - directoryuri + - groupname + - groupdesc @version 9.2 @author Allan Bowe @@ -20,7 +20,7 @@ **/ %macro mm_getDirectories( - path= + path= ,outds=work.mm_getDirectories ,mDebug=0 )/*/STORE SOURCE*/; @@ -39,8 +39,10 @@ data &outds (keep=directoryuri name directoryname directorydesc ); do while (metadata_getnobj("omsobj:Directory?@Id contains '.'",__i,directoryuri)>0); %end; %else %do; - do while - (metadata_getnobj("omsobj:Directory?@DirectoryName='&path'",__i,directoryuri)>0); + do while( + metadata_getnobj("omsobj:Directory?@DirectoryName='&path'",__i,directoryuri) + >0 + ); %end; __rc1=metadata_getattr(directoryuri, "Name", name); __rc2=metadata_getattr(directoryuri, "DirectoryName", directoryname); diff --git a/meta/mm_getdocument.sas b/meta/mm_getdocument.sas index d5255b3..dc734da 100644 --- a/meta/mm_getdocument.sas +++ b/meta/mm_getdocument.sas @@ -37,8 +37,8 @@ %&mD.put _local_; /** - * check tree exists - */ + * check tree exists + */ data _null_; length type uri $256; @@ -54,8 +54,8 @@ run; ) /** - * Check object exists - */ + * Check object exists + */ data _null_; length type docuri tsuri tsid $256 ; rc1=metadata_pathobj("","&tree/&name","Note",type,docuri); @@ -73,14 +73,14 @@ run; ) /** - * Now we can extract the textstore - */ + * Now we can extract the textstore + */ filename __getdoc temp lrecl=10000000; proc metadata - in="$METAREPOSITORY - - SAS1" - out=__getdoc ; + in="$METAREPOSITORY + + SAS1" + out=__getdoc ; run; /* find the beginning of the text */ @@ -98,47 +98,47 @@ data _null_; /* read the content, byte by byte, resolving escaped chars */ filename __outdoc "&outref" lrecl=100000; data _null_; - length filein 8 fileid 8; - filein = fopen("__getdoc","I",1,"B"); - fileid = fopen("__outdoc","O",1,"B"); - rec = "20"x; - length entity $6; - do while(fread(filein)=0); - x+1; - if x>&start then do; - rc = fget(filein,rec,1); - if rec='"' then leave; - else if rec="&" then do; - entity=rec; - do until (rec=";"); - if fread(filein) ne 0 then goto getout; - rc = fget(filein,rec,1); - entity=cats(entity,rec); + length filein 8 fileid 8; + filein = fopen("__getdoc","I",1,"B"); + fileid = fopen("__outdoc","O",1,"B"); + rec = "20"x; + length entity $6; + do while(fread(filein)=0); + x+1; + if x>&start then do; + rc = fget(filein,rec,1); + if rec='"' then leave; + else if rec="&" then do; + entity=rec; + do until (rec=";"); + if fread(filein) ne 0 then goto getout; + rc = fget(filein,rec,1); + entity=cats(entity,rec); + end; + select (entity); + when ('&' ) rec='&' ; + when ('<' ) rec='<' ; + when ('>' ) rec='>' ; + when (''') rec="'" ; + when ('"') rec='"' ; + when (' ') rec='0A'x; + when (' ') rec='0D'x; + when ('$' ) rec='$' ; + when (' ') rec='09'x; + otherwise putlog "WARNING: missing value for " entity=; + end; + rc =fput(fileid, substr(rec,1,1)); + rc =fwrite(fileid); end; - select (entity); - when ('&' ) rec='&' ; - when ('<' ) rec='<' ; - when ('>' ) rec='>' ; - when (''') rec="'" ; - when ('"') rec='"' ; - when (' ') rec='0A'x; - when (' ') rec='0D'x; - when ('$' ) rec='$' ; - when (' ') rec='09'x; - otherwise putlog "WARNING: missing value for " entity=; + else do; + rc =fput(fileid,rec); + rc =fwrite(fileid); end; - rc =fput(fileid, substr(rec,1,1)); - rc =fwrite(fileid); end; - else do; - rc =fput(fileid,rec); - rc =fwrite(fileid); - end; - end; - end; - getout: - rc=fclose(filein); - rc=fclose(fileid); + end; + getout: + rc=fclose(filein); + rc=fclose(fileid); run; filename __getdoc clear; filename __outdoc clear; diff --git a/meta/mm_getfoldermembers.sas b/meta/mm_getfoldermembers.sas index c72df20..0e512e8 100644 --- a/meta/mm_getfoldermembers.sas +++ b/meta/mm_getfoldermembers.sas @@ -11,7 +11,8 @@ %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 [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

Data Outputs

@@ -20,11 +21,11 @@ |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.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|

SAS Macros

@@ -37,7 +38,7 @@ **/ %macro mm_getfoldermembers( - root= + root= ,outds=work.mm_getfoldertree )/*/STORE SOURCE*/; diff --git a/meta/mm_getfoldertree.sas b/meta/mm_getfoldertree.sas index 96e13bb..b629155 100644 --- a/meta/mm_getfoldertree.sas +++ b/meta/mm_getfoldertree.sas @@ -13,7 +13,8 @@ options notes source; @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 [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

SAS Macros

@@ -23,7 +24,7 @@ **/ %macro mm_getfoldertree( - root= + root= ,outds=work.mm_getfoldertree ,mDebug=0 ,depth=50 /* how many nested folders to query */ diff --git a/meta/mm_getgroupmembers.sas b/meta/mm_getgroupmembers.sas index 60c4390..503a392 100755 --- a/meta/mm_getgroupmembers.sas +++ b/meta/mm_getgroupmembers.sas @@ -2,11 +2,11 @@ @file @brief Creates dataset with all members of a metadata group @details - + usage: - + %mm_getgroupmembers(someGroupName - ,outds=work.mm_getgroupmembers + ,outds=work.mm_getgroupmembers ,emails=YES) @param group metadata group for which to bring back members diff --git a/meta/mm_getgroups.sas b/meta/mm_getgroups.sas index 7bd126a..5e4386d 100755 --- a/meta/mm_getgroups.sas +++ b/meta/mm_getgroups.sas @@ -11,14 +11,15 @@ @param [in] user= the metadata user to return groups for. Leave blank for all 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 [out] outds= the dataset to create that contains the list of groups @returns outds dataset containing all groups in a column named "metagroup" - - groupuri - - groupname - - groupdesc + - groupuri + - groupname + - groupdesc @version 9.2 @author Allan Bowe @@ -26,7 +27,7 @@ **/ %macro mm_getGroups( - user= + user= ,outds=work.mm_getGroups ,repo=foundation ,mDebug=0 @@ -39,7 +40,8 @@ %&mD.put Executing mm_getGroups.sas; %&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; options metarepository=&repo; %end; diff --git a/meta/mm_getlibs.sas b/meta/mm_getlibs.sas index 9cfc904..fe819d0 100755 --- a/meta/mm_getlibs.sas +++ b/meta/mm_getlibs.sas @@ -46,7 +46,7 @@ run; filename response temp; /* get list of libraries */ proc metadata in= - ' + ' $METAREPOSITORY SASLibrary diff --git a/meta/mm_getobjects.sas b/meta/mm_getobjects.sas index 5cce557..fcac368 100644 --- a/meta/mm_getobjects.sas +++ b/meta/mm_getobjects.sas @@ -28,10 +28,10 @@ filename response temp; /* get list of libraries */ proc metadata in= - "$METAREPOSITORY - &typeSAS - 0" - out=response; + "$METAREPOSITORY + &typeSAS + 0" + out=response; run; /* write the response to the log for debugging */ @@ -46,7 +46,8 @@ filename sxlemap temp; data _null_; file sxlemap; put '
'; - put "/GetMetadataObjects/Objects/&type"; + put "/GetMetadataObjects/Objects/&type"; + put ""; put ''; put "/GetMetadataObjects/Objects/&type/@Id"; put "characterstring200"; diff --git a/meta/mm_getpublictypes.sas b/meta/mm_getpublictypes.sas index 42e40cf..7dbd344 100644 --- a/meta/mm_getpublictypes.sas +++ b/meta/mm_getpublictypes.sas @@ -1,8 +1,9 @@ /** @file mm_getpublictypes.sas @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: * dataset will contain one column - publictype ($64); diff --git a/meta/mm_getrepos.sas b/meta/mm_getrepos.sas index aa102e6..241af34 100644 --- a/meta/mm_getrepos.sas +++ b/meta/mm_getrepos.sas @@ -26,8 +26,8 @@ filename response temp; /* get list of libraries */ proc metadata in= - "1" - out=response; + "1" + out=response; run; /* write the response to the log for debugging */ @@ -44,61 +44,76 @@ filename sxlemap temp; data _null_; file sxlemap; put '
'; - put "/GetRepositories/Repositories/Repository"; + put "/GetRepositories/Repositories/Repository"; + put ""; put ''; - put "/GetRepositories/Repositories/Repository/@Id"; + put "/GetRepositories/Repositories/Repository/@Id"; + put ""; put "characterstring200"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@Name"; + put "/GetRepositories/Repositories/Repository/@Name"; + put ""; put "characterstring200"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@Desc"; + put "/GetRepositories/Repositories/Repository/@Desc"; + put ""; put "characterstring200"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@DefaultNS"; + put ""; + put "/GetRepositories/Repositories/Repository/@DefaultNS"; put "characterstring200"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@RepositoryType"; + put ""; + put "/GetRepositories/Repositories/Repository/@RepositoryType"; put "characterstring20"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@RepositoryFormat"; + put ""; + put "/GetRepositories/Repositories/Repository/@RepositoryFormat"; put "characterstring10"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@Access"; + put ""; + put "/GetRepositories/Repositories/Repository/@Access"; put "characterstring16"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@CurrentAccess"; + put ""; + put "/GetRepositories/Repositories/Repository/@CurrentAccess"; put "characterstring16"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@PauseState"; + put ""; + put "/GetRepositories/Repositories/Repository/@PauseState"; put "characterstring16"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@Path"; + put "/GetRepositories/Repositories/Repository/@Path"; + put ""; put "characterstring256"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@Engine"; + put "/GetRepositories/Repositories/Repository/@Engine"; + put ""; put "characterstring8"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@Options"; + put "/GetRepositories/Repositories/Repository/@Options"; + put ""; put "characterstring32"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@MetadataCreated"; + put ""; + put "/GetRepositories/Repositories/Repository/@MetadataCreated"; put "characterstring24"; put ''; put ''; - put "/GetRepositories/Repositories/Repository/@MetadataUpdated"; + put ""; + put "/GetRepositories/Repositories/Repository/@MetadataUpdated"; put "characterstring24"; put ''; put '
'; diff --git a/meta/mm_getroles.sas b/meta/mm_getroles.sas index f41d23d..82d457c 100644 --- a/meta/mm_getroles.sas +++ b/meta/mm_getroles.sas @@ -31,28 +31,32 @@ filename response temp; options noquotelenmax; proc metadata in= '$METAREPOSITORY - IdentityGroupSAS388 - - - - - ' - out=response; + IdentityGroupSAS388 + + + + + ' + out=response; run; filename sxlemap temp; data _null_; file sxlemap; put ''; - put "/GetMetadataObjects/Objects/IdentityGroup"; + put "/GetMetadataObjects/Objects/IdentityGroup"; + put ""; put ''; - put "/GetMetadataObjects/Objects/IdentityGroup/@Id"; + put "/GetMetadataObjects/Objects/IdentityGroup/@Id"; + put ""; put "characterstring32"; put ''; - put "/GetMetadataObjects/Objects/IdentityGroup/@Name"; + put "/GetMetadataObjects/Objects/IdentityGroup/@Name"; + put ""; put "characterstring256"; put ''; - put "/GetMetadataObjects/Objects/IdentityGroup/@Desc"; + put "/GetMetadataObjects/Objects/IdentityGroup/@Desc"; + put ""; put "characterstring500"; put '
'; run; diff --git a/meta/mm_getstpcode.sas b/meta/mm_getstpcode.sas index 662ec8f..7de5ac0 100644 --- a/meta/mm_getstpcode.sas +++ b/meta/mm_getstpcode.sas @@ -69,14 +69,14 @@ run; /** - * Now we can extract the textstore - */ + * Now we can extract the textstore + */ filename __getdoc temp lrecl=10000000; proc metadata - in="$METAREPOSITORY - - SAS1" - out=__getdoc ; + in="$METAREPOSITORY + + SAS1" + out=__getdoc ; run; /* find the beginning of the text */ @@ -97,47 +97,47 @@ data _null_; /* read the content, byte by byte, resolving escaped chars */ filename __outdoc &outeng lrecl=100000; data _null_; - length filein 8 fileid 8; - filein = fopen("__getdoc","I",1,"B"); - fileid = fopen("__outdoc","O",1,"B"); - rec = "20"x; - length entity $6; - do while(fread(filein)=0); - x+1; - if x>&start then do; - rc = fget(filein,rec,1); - if rec='"' then leave; - else if rec="&" then do; - entity=rec; - do until (rec=";"); - if fread(filein) ne 0 then goto getout; - rc = fget(filein,rec,1); - entity=cats(entity,rec); + length filein 8 fileid 8; + filein = fopen("__getdoc","I",1,"B"); + fileid = fopen("__outdoc","O",1,"B"); + rec = "20"x; + length entity $6; + do while(fread(filein)=0); + x+1; + if x>&start then do; + rc = fget(filein,rec,1); + if rec='"' then leave; + else if rec="&" then do; + entity=rec; + do until (rec=";"); + if fread(filein) ne 0 then goto getout; + rc = fget(filein,rec,1); + entity=cats(entity,rec); + end; + select (entity); + when ('&' ) rec='&' ; + when ('<' ) rec='<' ; + when ('>' ) rec='>' ; + when (''') rec="'" ; + when ('"') rec='"' ; + when (' ') rec='0A'x; + when (' ') rec='0D'x; + when ('$' ) rec='$' ; + when (' ') rec='09'x; + otherwise putlog "%str(WARN)ING: missing value for " entity=; + end; + rc =fput(fileid, substr(rec,1,1)); + rc =fwrite(fileid); end; - select (entity); - when ('&' ) rec='&' ; - when ('<' ) rec='<' ; - when ('>' ) rec='>' ; - when (''') rec="'" ; - when ('"') rec='"' ; - when (' ') rec='0A'x; - when (' ') rec='0D'x; - when ('$' ) rec='$' ; - when (' ') rec='09'x; - otherwise putlog "%str(WARN)ING: missing value for " entity=; + else do; + rc =fput(fileid,rec); + rc =fwrite(fileid); end; - rc =fput(fileid, substr(rec,1,1)); - rc =fwrite(fileid); end; - else do; - rc =fput(fileid,rec); - rc =fwrite(fileid); - end; - end; - end; - getout: - rc=fclose(filein); - rc=fclose(fileid); + end; + getout: + rc=fclose(filein); + rc=fclose(fileid); run; %if &outeng=TEMP %then %do; diff --git a/meta/mm_getstps.sas b/meta/mm_getstps.sas index ebe1787..29621cb 100644 --- a/meta/mm_getstps.sas +++ b/meta/mm_getstps.sas @@ -23,16 +23,17 @@ combine with the tree= parameter. @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 showDesc= provide a non blank value to return stored process descriptions - @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). + @param showDesc= provide a non blank value to return stored process + descriptions + @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 - - stpuri - - stpname - - treeuri - - stpdesc (if requested) - - usageversion (if requested) + - stpuri + - stpname + - treeuri + - stpdesc (if requested) + - usageversion (if requested) @version 9.2 @author Allan Bowe @@ -40,7 +41,7 @@ **/ %macro mm_getstps( - tree= + tree= ,name= ,outds=work.mm_getstps ,mDebug=0 diff --git a/meta/mm_gettableid.sas b/meta/mm_gettableid.sas index 77143f9..08d797c 100644 --- a/meta/mm_gettableid.sas +++ b/meta/mm_gettableid.sas @@ -1,7 +1,7 @@ /** @file mm_gettableid.sas @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. Usage: @@ -22,7 +22,7 @@ **/ %macro mm_gettableid( - libref= + libref= ,ds= ,outds=work.mm_gettableid ,mDebug=0 @@ -52,7 +52,7 @@ data &outds; if type='DatabaseSchema' then tmpuri=usingpkguri; else tmpuri=uri; - + t=1; do while(metadata_getnasn(tmpuri, "Tables", t, tableuri)>0); t+1; diff --git a/meta/mm_gettree.sas b/meta/mm_gettree.sas index dedf21c..4baca6a 100755 --- a/meta/mm_gettree.sas +++ b/meta/mm_gettree.sas @@ -16,8 +16,8 @@ @param mDebug= set to 1 to show debug messages in the log @returns outds dataset containing the following columns: - - treeuri - - treepath + - treeuri + - treepath @version 9.2 @author Allan Bowe @@ -25,7 +25,7 @@ **/ %macro mm_getTree( - tree= + tree= ,inds= ,outds=work.mm_getTree ,mDebug=0 diff --git a/meta/mm_gettypes.sas b/meta/mm_gettypes.sas index c8a719d..eaf0582 100644 --- a/meta/mm_gettypes.sas +++ b/meta/mm_gettypes.sas @@ -26,16 +26,16 @@ filename response temp; /* get list of libraries */ proc metadata in= - ' - - SAS - - 2048 - - - $METAREPOSITORY - -' + ' + + SAS + + 2048 + + + $METAREPOSITORY + + ' out=response; run; diff --git a/meta/mm_getusers.sas b/meta/mm_getusers.sas index f8a7a43..2717e5b 100644 --- a/meta/mm_getusers.sas +++ b/meta/mm_getusers.sas @@ -31,16 +31,16 @@ filename response temp; proc metadata in= ' - $METAREPOSITORY - Person - SAS - 0 - - - - - - ' + $METAREPOSITORY + Person + SAS + 0 + + + + + + ' out=response; run; @@ -48,7 +48,8 @@ filename sxlemap temp; data _null_; file sxlemap; put ''; - put "/GetMetadataObjects/Objects/Person"; + put "/GetMetadataObjects/Objects/Person"; + put ""; put ''; put "/GetMetadataObjects/Objects/Person/@Id"; put "characterstring32"; diff --git a/meta/mm_getwebappsrvprops.sas b/meta/mm_getwebappsrvprops.sas index 7b3ff4a..57c1f22 100644 --- a/meta/mm_getwebappsrvprops.sas +++ b/meta/mm_getwebappsrvprops.sas @@ -33,23 +33,23 @@ filename __in temp lrecl=10000; filename __out temp lrecl=10000; filename __shake temp lrecl=10000; data _null_ ; - file __in ; - put '' ; - put '$METAREPOSITORY' ; - put 'TextStore' ; - put 'SAS' ; - put '388' ; - put '' ; - put ''; - put '' ; - put '' ; - put '' ; - put '' ; - put '' ; - put '' ; + file __in ; + put '' ; + put '$METAREPOSITORY' ; + put 'TextStore' ; + put 'SAS' ; + put '388' ; + put '' ; + put ''; + put '' ; + put '' ; + put '' ; + put '' ; + put '' ; + put '' ; run ; proc metadata in=__in out=__out verbose;run; diff --git a/meta/mm_spkexport.sas b/meta/mm_spkexport.sas index df84b7f..c8b223a 100644 --- a/meta/mm_spkexport.sas +++ b/meta/mm_spkexport.sas @@ -1,7 +1,7 @@ /** @file mm_spkexport.sas @brief Creates an batch spk export command - @details Creates a script that will export everything in a metadata folder to + @details Creates a script that will export everything in a metadata folder to a specified location. If you have XCMD enabled, then you can use mmx_spkexport (which performs the actual export) @@ -12,7 +12,8 @@ Usage: %* 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; %* create sample text file as input to the macro; @@ -34,7 +35,7 @@ filename myref "/tmp/mmscript.sh"; %mm_spkexport(metaloc=%str(/my/meta/loc) - outref=myref + outref=myref ,cmdoutloc=%str(/tmp) ,cmdoutname=mmx ) @@ -56,9 +57,9 @@ @param secureref= fileref containing the username / password (should point to a file in a secure location). Leave blank to substitute $bash type vars. @param outref= fileref to which to write the command - @param cmdoutloc= the directory to which the command will write the SPK + @param cmdoutloc= the directory to which the command will write the SPK (default=WORK) - @param cmdoutname= the name of the spk / log files to create (will be + @param cmdoutname= the name of the spk / log files to create (will be identical just with .spk or .log extension) @version 9.4 @@ -92,7 +93,8 @@ %let port=%sysfunc(getoption(metaport)); %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) diff --git a/meta/mm_tree.sas b/meta/mm_tree.sas index f752535..708998c 100644 --- a/meta/mm_tree.sas +++ b/meta/mm_tree.sas @@ -15,7 +15,8 @@ Usage: %* 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; %* export everything; @@ -32,16 +33,16 @@ %* with specific types; %mm_tree(root=%str(/my/folder) - ,types= - DeployedJob - ExternalFile - Folder - Folder.SecuredData - GeneratedTransform - InformationMap.Relational - Job - Library - Prompt + ,types= + DeployedJob + ExternalFile + Folder + Folder.SecuredData + GeneratedTransform + InformationMap.Relational + Job + Library + Prompt StoredProcess Table ,outds=morestuff) @@ -53,8 +54,8 @@ @param root= the parent folder under which to return all contents @param outds= the dataset to create that contains the list of directories - @param types= Space-seperated, unquoted list of types for filtering the - output. Special types: + @param types= Space-seperated, unquoted list of types for filtering the + output. Special types: * ALl - return all types (the default) * EXPORTABLE - return only the content types that can be exported in an SPK @@ -64,7 +65,7 @@ **/ %macro mm_tree( - root= + root= ,types=ALL ,outds=work.mm_tree )/*/STORE SOURCE*/; @@ -84,12 +85,12 @@ options noquotelenmax; filename response temp; /* get list of libraries */ proc metadata in= - '$METAREPOSITORY - TreeSAS - 384 - - ' - out=response; + '$METAREPOSITORY + TreeSAS + 384 + + ' + out=response; run; /* data _null_; @@ -104,7 +105,8 @@ filename sxlemap temp; data _null_; file sxlemap; put '
'; - put "/GetMetadataObjects/Objects/Tree"; + put "/GetMetadataObjects/Objects/Tree"; + put ""; put ''; put "/GetMetadataObjects/Objects/Tree/@Id"; put "characterstring64"; @@ -130,7 +132,7 @@ data &outds; path=cats('/',pname,path); tmpuri=parenturi; end; - + if path=:"&root"; %if "&types"="ALL" or ("&types" ne "ALL" and "&types" ne "Folder") %then %do; diff --git a/meta/mm_updatestpservertype.sas b/meta/mm_updatestpservertype.sas index 0b93e47..387d42f 100644 --- a/meta/mm_updatestpservertype.sas +++ b/meta/mm_updatestpservertype.sas @@ -10,8 +10,8 @@ @param target= full path to the STP being deleted - @param type= Either WKS or STP depending on whether Workspace or Stored Process - type required + @param type= Either WKS or STP depending on whether Workspace or + Stored Process type required @version 9.4 @author Allan Bowe @@ -24,8 +24,8 @@ )/*/STORE SOURCE*/; /** - * Check STP does exist - */ + * Check STP does exist + */ %local cmtype; data _null_; length type uri $256; @@ -51,7 +51,8 @@ data _null_; n+1; rc=metadata_getattr(uri,"Name",name); if name='Stored Process' then do; - rc = METADATA_SETATTR(uri,'StoredText','' + rc = METADATA_SETATTR(uri,'StoredText' + ,'' !!''); diff --git a/meta/mm_webout.sas b/meta/mm_webout.sas index 6b39929..6c9fe26 100644 --- a/meta/mm_webout.sas +++ b/meta/mm_webout.sas @@ -33,7 +33,7 @@ **/ %macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y); -%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug +%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug sasjs_tables; %local i tempds; @@ -108,7 +108,7 @@ i+1; call symputx('wt'!!left(i),name,'l'); call symputx('wtcnt',i,'l'); - data _null_; file &fref encoding='utf-8'; + data _null_; file &fref encoding='utf-8'; put ",""WORK"":{"; %do i=1 %to &wtcnt; %let wt=&&wt&i; diff --git a/metax/mmx_spkexport.sas b/metax/mmx_spkexport.sas index 5884b36..625b255 100644 --- a/metax/mmx_spkexport.sas +++ b/metax/mmx_spkexport.sas @@ -20,7 +20,8 @@ Usage: run; 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 ,outspkpath=%str(/tmp) ) @@ -34,7 +35,7 @@ Usage: @param metaloc= the metadata folder to export @param secureref= fileref containing the username / password (should point to a file in a secure location) - @param outspkname= name of the spk to be created (default is mmxport). + @param outspkname= name of the spk to be created (default is mmxport). @param outspkpath= directory in which to create the SPK. Default is WORK. @version 9.4 @@ -56,7 +57,8 @@ Usage: /* get creds */ %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) diff --git a/sasjs/doxy/new_stylesheet.css b/sasjs/doxy/new_stylesheet.css index 41958a7..0e9f915 100644 --- a/sasjs/doxy/new_stylesheet.css +++ b/sasjs/doxy/new_stylesheet.css @@ -1,5 +1,5 @@ #projectlogo img { - border: 0px none; - max-height:70px + border: 0px none; + max-height:70px } \ No newline at end of file diff --git a/viya/mv_createwebservice.sas b/viya/mv_createwebservice.sas index 7f7b96b..d531296 100644 --- a/viya/mv_createwebservice.sas +++ b/viya/mv_createwebservice.sas @@ -258,7 +258,8 @@ data _null_; put ' %end; '; put ' data _null_;file &jref mod ; '; 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 ' by varnum; '; put ' '; @@ -297,7 +298,8 @@ data _null_; put ' %end; '; put ' %end; '; 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 ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; '; put ' set &tempds; '; @@ -412,7 +414,8 @@ data _null_; put ' if _n_=1 then call symputx(''input_statement'',_infile_); '; put ' list; '; 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 ' run; '; put ' %end; '; @@ -444,7 +447,7 @@ data _null_; put ' /* setup webout */ '; put ' OPTIONS NOBOMFILE; '; put ' %if "X&SYS_JES_JOB_URI.X"="XX" %then %do; '; - put ' filename _webout temp lrecl=999999 mod; '; + put ' filename _webout temp lrecl=999999 mod; '; put ' %end; '; put ' %else %do; '; put ' filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" '; @@ -453,7 +456,8 @@ data _null_; put ' '; put ' /* setup temp ref */ '; 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 ' '; put ' /* setup json */ '; From 9568b17f2077471750a8f0aa929c4a09be0f489c Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sat, 3 Apr 2021 21:34:40 +0200 Subject: [PATCH 23/70] 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. --- package-lock.json | 1586 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 10 +- 2 files changed, 1573 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 27ec6fb..918f583 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,35 +1,1579 @@ { "name": "@sasjs/core", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "@sasjs/core", - "version": "1.0.0", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { + "dependencies": { + "@sasjs/adapter": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-2.2.13.tgz", + "integrity": "sha512-4tgpythcv+o1WhCwnxk1kt5a/xJc5mnJt7F2ie3vKyAP7bCuYokAP8RS9mwfUw55cFcJtLhxbr+5+3+hA7BTtg==", + "dev": true, + "requires": { + "@sasjs/utils": "^2.6.3", + "axios": "^0.21.1", + "form-data": "^3.0.0", + "https": "^1.0.0" + } + }, + "@sasjs/cli": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-2.14.2.tgz", + "integrity": "sha512-Wnn2VtEqMtQ6zK+wwoRBWBQNzyD7nXS3p5TGx/f03QXTXYnLYBY2nqrjLs1NOWmi5tikQhnbMzdtD65eackDoQ==", + "dev": true, + "requires": { + "@sasjs/adapter": "^2.2.13", + "@sasjs/core": "^2.13.2", + "@sasjs/lint": "^1.2.0", + "@sasjs/utils": "^2.8.0", + "btoa": "^1.2.1", + "chalk": "^4.1.0", + "cli-table": "^0.3.6", + "csv-stringify": "^5.6.1", + "dotenv": "^8.2.0", + "esm": "^3.2.25", + "find": "^0.3.0", + "fs": "0.0.1-security", + "fs-extra": "^9.0.1", + "get-installed-path": "^4.0.8", + "jsdom": "^16.5.1", + "jwt-decode": "^3.1.2", + "lodash.groupby": "^4.6.0", + "lodash.uniqby": "^4.7.0", + "node-graphviz": "^0.1.0", + "ora": "^5.2.0", + "rimraf": "^3.0.2", + "shelljs": "^0.8.4" + } + }, + "@sasjs/core": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-2.15.0.tgz", + "integrity": "sha512-n6ru6QBe9TTnNmrmP5VPPARvUQenQ2K5eEfu0TGofInjTrhx2wcMiffG1Po0fQRNFyA3rHFxrHUISoloZYx8hw==", + "dev": true, + "requires": { "node-git-hooks": "^1.0.5" } }, - "node_modules/node-git-hooks": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/node-git-hooks/-/node-git-hooks-1.0.5.tgz", - "integrity": "sha512-05rULsJy8z2OvXTQFZv9fN20uo186EYgJYQjkL1OjnXj53QivOAGUzZilZ/MX8OmPRaN+deJBtlvMRydpdfnqA==", - "bin": { - "node-git-hooks": "bin/install.js" - }, - "engines": { - "node": ">=4.0.0" + "@sasjs/lint": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@sasjs/lint/-/lint-1.4.1.tgz", + "integrity": "sha512-865n0mVb6tQnu25X6iKGcSpadkbu7i4zXzY4zfSGx1B3ifCt+C8BLCeqd1EqvClsPfvpIYQWFPkDtWT9zofFTQ==", + "dev": true, + "requires": { + "@sasjs/utils": "^2.10.1" } - } - }, - "dependencies": { + }, + "@sasjs/utils": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.10.2.tgz", + "integrity": "sha512-N5nIsz7NUs1Yt0Am0QGs9UXDkN396ialCIfIRsNR9h4VtQRzvOwjXrsLnr3AUAAV9Z8h9CtadkC3W6MAzrQaOg==", + "dev": true, + "requires": { + "@types/prompts": "^2.0.10", + "consola": "^2.15.0", + "prompts": "^2.4.1", + "valid-url": "^1.0.9" + } + }, + "@types/node": { + "version": "14.14.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz", + "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==", + "dev": true + }, + "@types/prompts": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.0.10.tgz", + "integrity": "sha512-W3PEl3l4vmxdgfY6LUG7ysh+mLJOTOFYmSpiLe6MCo1OdEm8b5s6ZJfuTQgEpYNwcMiiaRzJespPS5Py2tqLlQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "dev": true + }, + "acorn": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.1.0.tgz", + "integrity": "sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA==", + "dev": true + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "dev": true, + "requires": { + "follow-redirects": "^1.10.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "dev": true + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz", + "integrity": "sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==", + "dev": true + }, + "cli-table": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.6.tgz", + "integrity": "sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==", + "dev": true, + "requires": { + "colors": "1.0.3" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "csv-stringify": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.2.tgz", + "integrity": "sha512-n3rIVbX6ylm1YsX2NEug9IaPV8xRnT+9/NNZbrA/bcHgOSSeqtWla6XnI/xmyu57wIw+ASCAoX1oM6EZtqJV0A==", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, + "decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + } + } + }, + "dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "find": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/find/-/find-0.3.0.tgz", + "integrity": "sha512-iSd+O4OEYV/I36Zl8MdYJO0xD82wH528SaCieTVHhclgiYNe9y+yPKSwK+A7/WsmHL1EZ+pYUJBXWTL5qofksw==", + "dev": true, + "requires": { + "traverse-chain": "~0.1.0" + } + }, + "follow-redirects": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", + "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=", + "dev": true + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-installed-path": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/get-installed-path/-/get-installed-path-4.0.8.tgz", + "integrity": "sha512-PmANK1xElIHlHH2tXfOoTnSDUjX1X3GvKK6ZyLbUnSCCn1pADwu67eVWttuPzJWrXDDT2MfO6uAaKILOFfitmA==", + "dev": true, + "requires": { + "global-modules": "1.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", + "integrity": "sha1-PDfHrhqO65ZpBKKtHpdaGUt+06Q=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "16.5.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.2.tgz", + "integrity": "sha512-JxNtPt9C1ut85boCbJmffaQ06NBnzkQY/MWO3YxPW8IWS38A26z+B1oBvA9LwKrytewdfymnhi4UNH3/RAgZrg==", + "dev": true, + "requires": { + "abab": "^2.0.5", + "acorn": "^8.1.0", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.9", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.4", + "xml-name-validator": "^3.0.0" + } + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=", + "dev": true + }, + "lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "mime-db": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", + "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", + "dev": true + }, + "mime-types": { + "version": "2.1.30", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", + "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", + "dev": true, + "requires": { + "mime-db": "1.47.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "node-git-hooks": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/node-git-hooks/-/node-git-hooks-1.0.5.tgz", - "integrity": "sha512-05rULsJy8z2OvXTQFZv9fN20uo186EYgJYQjkL1OjnXj53QivOAGUzZilZ/MX8OmPRaN+deJBtlvMRydpdfnqA==" + "integrity": "sha512-05rULsJy8z2OvXTQFZv9fN20uo186EYgJYQjkL1OjnXj53QivOAGUzZilZ/MX8OmPRaN+deJBtlvMRydpdfnqA==", + "dev": true + }, + "node-graphviz": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/node-graphviz/-/node-graphviz-0.1.0.tgz", + "integrity": "sha512-RhRb+HXYp8ZVVlsxW1/I61taWuQSS6x3o9GanZOdiMyP4TFaBHwfcn221/dZtmI4wdBgu/q7/Zt4jMgi3XQiGA==", + "dev": true + }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "ora": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.0.tgz", + "integrity": "sha512-1StwyXQGoU6gdjYkyVcqOLnVlbKj+6yPNNOxJVgpt9t4eksKjiriiHuxktLYkgllwk+D6MbC4ihH84L1udRXPg==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prompts": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", + "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, + "requires": { + "lodash": "^4.17.19" + } + }, + "request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "dev": true, + "requires": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "shelljs": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + }, + "dependencies": { + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } + } + }, + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "traverse-chain": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", + "integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, + "valid-url": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz", + "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==", + "dev": true, + "requires": { + "lodash": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^6.1.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", + "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==", + "dev": true + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true } } } diff --git a/package.json b/package.json index a16ffb5..d367938 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,13 @@ "docs": "sasjs doc && ./sasjs/utils/build.sh", "postinstall": "node-git-hooks" }, - "dependencies": { - "node-git-hooks": "^1.0.5" + "devDependencies": { + "@sasjs/cli": "^2.14.2", + "ghooks": "^2.0.4" + }, + "config": { + "ghooks": { + "pre-commit": "sasjs lint" + } } } From 7dec3120bea3cfac276bcb378f71b982949864a2 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sat, 3 Apr 2021 21:35:44 +0200 Subject: [PATCH 24/70] chore: dependencies --- package-lock.json | 112 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/package-lock.json b/package-lock.json index 918f583..47f9701 100644 --- a/package-lock.json +++ b/package-lock.json @@ -342,6 +342,12 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz", + "integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E=", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -554,6 +560,24 @@ "traverse-chain": "~0.1.0" } }, + "findup": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/findup/-/findup-0.1.5.tgz", + "integrity": "sha1-itkpozk7rGJ5V6fl3kYjsGsOLOs=", + "dev": true, + "requires": { + "colors": "~0.6.0-1", + "commander": "~2.1.0" + }, + "dependencies": { + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", + "dev": true + } + } + }, "follow-redirects": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", @@ -625,6 +649,20 @@ "assert-plus": "^1.0.0" } }, + "ghooks": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/ghooks/-/ghooks-2.0.4.tgz", + "integrity": "sha1-/VDgQP9UiQauQstReToBv+JFZ7k=", + "dev": true, + "requires": { + "findup": "0.1.5", + "lodash.clone": "4.5.0", + "manage-path": "2.0.0", + "opt-cli": "1.5.1", + "path-exists": "3.0.0", + "spawn-command": "0.0.2" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -669,6 +707,12 @@ "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", "dev": true }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -937,6 +981,18 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash._baseclone": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-4.5.7.tgz", + "integrity": "sha1-zkKt4IOE711i+nfDD2GkbmhvhDQ=", + "dev": true + }, + "lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=", + "dev": true + }, "lodash.groupby": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", @@ -959,6 +1015,12 @@ "is-unicode-supported": "^0.1.0" } }, + "manage-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/manage-path/-/manage-path-2.0.0.tgz", + "integrity": "sha1-9M+EV7km7u4qg7FzUBQUvHbrlZc=", + "dev": true + }, "mime-db": { "version": "1.47.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", @@ -1031,6 +1093,44 @@ "mimic-fn": "^2.1.0" } }, + "opt-cli": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/opt-cli/-/opt-cli-1.5.1.tgz", + "integrity": "sha1-BNtEexPJa5kusxaFJm9O0NlzbcI=", + "dev": true, + "requires": { + "commander": "2.9.0", + "lodash.clone": "4.3.2", + "manage-path": "2.0.0", + "spawn-command": "0.0.2-1" + }, + "dependencies": { + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": ">= 1.0.0" + } + }, + "lodash.clone": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.3.2.tgz", + "integrity": "sha1-5WsXa2gjp93jj38r9Y3n1ZcSAOk=", + "dev": true, + "requires": { + "lodash._baseclone": "~4.5.0" + } + }, + "spawn-command": { + "version": "0.0.2-1", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", + "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", + "dev": true + } + } + }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -1074,6 +1174,12 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1319,6 +1425,12 @@ "dev": true, "optional": true }, + "spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha1-lUThpDygRfhTGqwaSMspva5iM44=", + "dev": true + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", From cb8992dadeb5a836034b54142e14883a76cbfc38 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sat, 3 Apr 2021 21:54:50 +0200 Subject: [PATCH 25/70] fix: remove .githooks now we have sasjs lint --- .githooks/pre-commit | 44 -------------------------------------------- package-lock.json | 22 +++++++++++----------- package.json | 3 +-- 3 files changed, 12 insertions(+), 57 deletions(-) delete mode 100755 .githooks/pre-commit diff --git a/.githooks/pre-commit b/.githooks/pre-commit deleted file mode 100755 index 161b5b6..0000000 --- a/.githooks/pre-commit +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -# -# A hook script to verify that no filenames with capital letters are committed. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. -# -# Go through all the changed files (except for deleted and unmerged) - -# Save exit code of last executed action -exit_code=0 - -# Check if file is one of SAS|DDL|CSV|SH and check for uppercase letters -mime_pattern="\.(sas|ddl|csv|sh)" -# Check for capital letters only in file names -extra_pattern="(^|/)[^/]*([A-Z]+)[^/]*\.[A-Za-z]{3}$" -# Grep git diff of files to commit -files=$( git diff --cached --find-copies --find-renames --name-only --diff-filter=ACMRTXBU | - grep -Ei "$mime_pattern" | - grep -E "$extra_pattern" ) -echo "$files" -if [[ -n "$files" ]]; -then - echo - echo "Found files that contain capital letters." - echo "Please rename the following files in lowercase, and commit again:" - - for file in $files; do - echo -e '- \E[0;32m'"$file"'\033[0m' - done - # Abort commit - exit_code=1 -fi - -if [ "$exit_code" == "0" ]; then - echo - echo -e '\033[1m'"Pre-commit validation Passed"'\033[0m' - echo -else - echo - echo -e '\033[1m'"Commit Aborted!"'\033[0m' - echo -fi -exit $exit_code \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 47f9701..3023ba5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -304,6 +304,14 @@ "dev": true, "requires": { "colors": "1.0.3" + }, + "dependencies": { + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + } } }, "clone": { @@ -328,9 +336,9 @@ "dev": true }, "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", "dev": true }, "combined-stream": { @@ -568,14 +576,6 @@ "requires": { "colors": "~0.6.0-1", "commander": "~2.1.0" - }, - "dependencies": { - "colors": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", - "dev": true - } } }, "follow-redirects": { diff --git a/package.json b/package.json index d367938..8629fbf 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,7 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "docs": "sasjs doc && ./sasjs/utils/build.sh", - "postinstall": "node-git-hooks" + "docs": "sasjs doc && ./sasjs/utils/build.sh" }, "devDependencies": { "@sasjs/cli": "^2.14.2", From 921354dac7972977768452d59cf7db1f271d6a7f Mon Sep 17 00:00:00 2001 From: Allan Bowe <4420615+allanbowe@users.noreply.github.com> Date: Mon, 5 Apr 2021 12:59:31 +0100 Subject: [PATCH 26/70] Update .gitpod.yml --- .gitpod.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitpod.yml b/.gitpod.yml index 549bf82..d3703b7 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,8 +1,8 @@ tasks: - - init: npm i && clear + - init: npm i image: file: .gitpod.dockerfile vscode: extensions: - - sasjs.sasjs-for-vscode@1.7.2:R6y1nzpFh2P99BZg5FgH5g== \ No newline at end of file + - sasjs.sasjs-for-vscode@1.7.2:R6y1nzpFh2P99BZg5FgH5g== From 961dd54ee0dcff6a6d6cdd54ea1ab989e16a8719 Mon Sep 17 00:00:00 2001 From: Allan Bowe <4420615+allanbowe@users.noreply.github.com> Date: Mon, 5 Apr 2021 13:29:39 +0100 Subject: [PATCH 27/70] Update .gitpod.dockerfile --- .gitpod.dockerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile index 1a4cf66..3c3a1fd 100644 --- a/.gitpod.dockerfile +++ b/.gitpod.dockerfile @@ -1,4 +1,3 @@ - FROM gitpod/workspace-full RUN sudo apt-get update \ @@ -6,5 +5,4 @@ RUN sudo apt-get update \ doxygen \ && npm i -g npm@latest \ && npm i -g @sasjs/cli \ - && npm i \ - && sudo rm -rf /var/lib/apt/lists/* \ No newline at end of file + && sudo rm -rf /var/lib/apt/lists/* From 951aa474f2ac53c578421f0d2c6c273558c53fce Mon Sep 17 00:00:00 2001 From: Allan Bowe <4420615+allanbowe@users.noreply.github.com> Date: Mon, 5 Apr 2021 14:16:22 +0100 Subject: [PATCH 28/70] Update .gitpod.dockerfile --- .gitpod.dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile index 3c3a1fd..eb685b9 100644 --- a/.gitpod.dockerfile +++ b/.gitpod.dockerfile @@ -3,6 +3,4 @@ FROM gitpod/workspace-full RUN sudo apt-get update \ && sudo apt-get install -y \ doxygen \ - && npm i -g npm@latest \ - && npm i -g @sasjs/cli \ && sudo rm -rf /var/lib/apt/lists/* From 49fbc210adf46d0248da2fed18bf2cd88d531f3c Mon Sep 17 00:00:00 2001 From: Allan Bowe <4420615+allanbowe@users.noreply.github.com> Date: Mon, 5 Apr 2021 14:22:58 +0100 Subject: [PATCH 29/70] Update .gitpod.yml --- .gitpod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitpod.yml b/.gitpod.yml index d3703b7..46e5719 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,5 +1,5 @@ tasks: - - init: npm i + - init: npm i -g @sasjs/cli image: file: .gitpod.dockerfile From 066ed00e44c18fb559b71fb934f0101863ce107e Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Mon, 5 Apr 2021 13:31:09 +0000 Subject: [PATCH 30/70] chore: reducing line length in lint by 5 characters --- .sasjslint | 2 +- all.sas | 9 +++++---- base/mp_resetoption.sas | 3 ++- viya/mv_createjob.sas | 6 +++--- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.sasjslint b/.sasjslint index a4cc7cb..d0e0091 100644 --- a/.sasjslint +++ b/.sasjslint @@ -3,7 +3,7 @@ "noEncodedPasswords": true, "hasDoxygenHeader": true, "noSpacesInFileNames": true, - "maxLineLength": 140, + "maxLineLength": 135, "lowerCaseFileNames": true, "noTabIndentation": true, "indentationMultiple": 2 diff --git a/all.sas b/all.sas index 7f97468..e56c48e 100644 --- a/all.sas +++ b/all.sas @@ -4472,7 +4472,8 @@ insert into &outds select distinct * from &append_ds; /** @file @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: options obs=30; @@ -11430,13 +11431,13 @@ options noquotelenmax; 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 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 +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) diff --git a/base/mp_resetoption.sas b/base/mp_resetoption.sas index ef2f87e..1235997 100644 --- a/base/mp_resetoption.sas +++ b/base/mp_resetoption.sas @@ -1,7 +1,8 @@ /** @file @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: options obs=30; diff --git a/viya/mv_createjob.sas b/viya/mv_createjob.sas index 0077b98..2411526 100644 --- a/viya/mv_createjob.sas +++ b/viya/mv_createjob.sas @@ -39,13 +39,13 @@ 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 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 +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) From a7fdb52231ac9f0038cdcc7aa590bf195b32095a Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Mon, 5 Apr 2021 18:26:31 +0000 Subject: [PATCH 31/70] fix: sasmeta vs basesas results --- all.sas | 10 ++++++---- base/mf_getplatform.sas | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/all.sas b/all.sas index e56c48e..a2a2298 100644 --- a/all.sas +++ b/all.sas @@ -573,21 +573,23 @@ options noquotelenmax; or "&sysprocessmode"= "SAS Compute Server" %then %do; SASVIYA %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 %return; %end; %else %do; - SAS + BASESAS %return; %end; %end; - %else %if %symexist(_metaport) %then %do; + %else %if %symexist(_metaport) or %symexist(_metauser) %then %do; SASMETA %return; %end; %else %do; - SAS + BASESAS %return; %end; %end; diff --git a/base/mf_getplatform.sas b/base/mf_getplatform.sas index 6ef0288..91bb447 100644 --- a/base/mf_getplatform.sas +++ b/base/mf_getplatform.sas @@ -27,21 +27,23 @@ or "&sysprocessmode"= "SAS Compute Server" %then %do; SASVIYA %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 %return; %end; %else %do; - SAS + BASESAS %return; %end; %end; - %else %if %symexist(_metaport) %then %do; + %else %if %symexist(_metaport) or %symexist(_metauser) %then %do; SASMETA %return; %end; %else %do; - SAS + BASESAS %return; %end; %end; From 456d10a90e6b44517420fa0d777407e37cff3679 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Wed, 7 Apr 2021 22:28:42 +0000 Subject: [PATCH 32/70] fix: switching to data step for JSON generation in mp_jsonout and the sasjs/adapter for improved reliability when data contains special characters. Closes #12 --- base/mp_jsonout.sas | 23 ++++++++++++++--------- viya/mv_webout.sas | 6 +++--- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/base/mp_jsonout.sas b/base/mp_jsonout.sas index 1ac804d..3b12e85 100644 --- a/base/mp_jsonout.sas +++ b/base/mp_jsonout.sas @@ -4,8 +4,11 @@ @details PROC JSON is faster but will produce errs like the ones below if special chars are encountered. - >An object or array close is not valid at this point in the JSON text. - >Date value out of range + > ERROR: Some code points did not transcode. + + > An object or array close is not valid at this point in the JSON text. + + > Date value out of range If this happens, try running with ENGINE=DATASTEP. @@ -14,7 +17,9 @@ filename tmp temp; data class; set sashelp.class;run; + %mp_jsonout(OPEN,jref=tmp) %mp_jsonout(OBJ,class,jref=tmp) + %mp_jsonout(CLOSE,jref=tmp) data _null_; infile tmp; @@ -27,18 +32,18 @@ For more information see https://sasjs.io @param action Valid values: - * OPEN - opens the JSON - * OBJ - sends a table with each row as an object - * ARR - sends a table with each row in an array - * CLOSE - closes the JSON + @li OPEN - opens the JSON + @li OBJ - sends a table with each row as an object + @li ARR - sends a table with each row in an array + @li CLOSE - closes the JSON @param ds the dataset to send. Must be a work table. @param jref= the fileref to which to send the JSON @param dslabel= the name to give the table in the exported JSON @param fmt= Whether to keep or strip formats from the table - @param engine= Which engine to use to send the JSON, options are: - * PROCJSON (default) - * DATASTEP + @param engine= Which engine to use to send the JSON, valid options are: + @li PROCJSON (default) + @li DATASTEP (more reliable when data has non standard characters) @param dbg= DEPRECATED - was used to conditionally add PRETTY to proc json but this can cause line truncation in large files. diff --git a/viya/mv_webout.sas b/viya/mv_webout.sas index 44f965b..6b3f77b 100644 --- a/viya/mv_webout.sas +++ b/viya/mv_webout.sas @@ -1,5 +1,5 @@ /** - @file mv_webout.sas + @file @brief Send data to/from the SAS Viya Job Execution Service @details This macro should be added to the start of each Job Execution Service, **immediately** followed by a call to: @@ -11,7 +11,7 @@ following syntax: data some datasets; * make some data ; - retain some columns; + retain some columns; run; %mv_webout(OPEN) @@ -162,7 +162,7 @@ %end; %else %if &action=ARR or &action=OBJ %then %do; %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt - ,jref=&fref,engine=PROCJSON,dbg=%str(&_debug) + ,jref=&fref,engine=DATASTEP,dbg=%str(&_debug) ) %end; %else %if &action=CLOSE %then %do; From 18be74a1c28e98deb09ba846571b871f8cf5eca0 Mon Sep 17 00:00:00 2001 From: rafgag <69139928+rafgag@users.noreply.github.com> Date: Thu, 8 Apr 2021 09:03:05 +0200 Subject: [PATCH 33/70] Update mp_jsonout.sas mod option added to the file statement in the last %else %if statement (&action=CLOSE) to avoid output file being overwritten --- base/mp_jsonout.sas | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/mp_jsonout.sas b/base/mp_jsonout.sas index 3b12e85..403cea8 100644 --- a/base/mp_jsonout.sas +++ b/base/mp_jsonout.sas @@ -165,8 +165,8 @@ %end; %else %if &action=CLOSE %then %do; - data _null_;file &jref encoding='utf-8'; + data _null_;file &jref encoding='utf-8' mod; put "}"; run; %end; -%mend; \ No newline at end of file +%mend; From af4dbb5632d6439c77f9aba27bd775279a5332e2 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Thu, 8 Apr 2021 09:49:50 +0200 Subject: [PATCH 34/70] feat: switching to DATASTEP over PROCJSON for json delivery in sasjs/adapter --- all.sas | 40 +++++++++++++++++++++--------------- meta/mm_createwebservice.sas | 2 +- viya/mv_createwebservice.sas | 4 ++-- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/all.sas b/all.sas index a2a2298..7acdb15 100644 --- a/all.sas +++ b/all.sas @@ -3919,8 +3919,11 @@ create table &outds (rename=( @details PROC JSON is faster but will produce errs like the ones below if special chars are encountered. - >An object or array close is not valid at this point in the JSON text. - >Date value out of range + > ERROR: Some code points did not transcode. + + > An object or array close is not valid at this point in the JSON text. + + > Date value out of range If this happens, try running with ENGINE=DATASTEP. @@ -3929,7 +3932,9 @@ create table &outds (rename=( filename tmp temp; data class; set sashelp.class;run; + %mp_jsonout(OPEN,jref=tmp) %mp_jsonout(OBJ,class,jref=tmp) + %mp_jsonout(CLOSE,jref=tmp) data _null_; infile tmp; @@ -3942,18 +3947,18 @@ create table &outds (rename=( For more information see https://sasjs.io @param action Valid values: - * OPEN - opens the JSON - * OBJ - sends a table with each row as an object - * ARR - sends a table with each row in an array - * CLOSE - closes the JSON + @li OPEN - opens the JSON + @li OBJ - sends a table with each row as an object + @li ARR - sends a table with each row in an array + @li CLOSE - closes the JSON @param ds the dataset to send. Must be a work table. @param jref= the fileref to which to send the JSON @param dslabel= the name to give the table in the exported JSON @param fmt= Whether to keep or strip formats from the table - @param engine= Which engine to use to send the JSON, options are: - * PROCJSON (default) - * DATASTEP + @param engine= Which engine to use to send the JSON, valid options are: + @li PROCJSON (default) + @li DATASTEP (more reliable when data has non standard characters) @param dbg= DEPRECATED - was used to conditionally add PRETTY to proc json but this can cause line truncation in large files. @@ -4075,11 +4080,12 @@ create table &outds (rename=( %end; %else %if &action=CLOSE %then %do; - data _null_;file &jref encoding='utf-8'; + data _null_;file &jref encoding='utf-8' mod; put "}"; run; %end; -%mend;/** +%mend; +/** @file @brief Convert all library members to CARDS files @details Gets list of members then calls the %mp_ds2cards() macro. @@ -7510,7 +7516,7 @@ data _null_; put '%end; '; put ' '; put '%else %if &action=CLOSE %then %do; '; - put ' data _null_;file &jref encoding=''utf-8''; '; + put ' data _null_;file &jref encoding=''utf-8'' mod; '; put ' put "}"; '; put ' run; '; put '%end; '; @@ -12037,7 +12043,7 @@ data _null_; put '%end; '; put ' '; put '%else %if &action=CLOSE %then %do; '; - put ' data _null_;file &jref encoding=''utf-8''; '; + put ' data _null_;file &jref encoding=''utf-8'' mod; '; put ' put "}"; '; put ' run; '; put '%end; '; @@ -12169,7 +12175,7 @@ data _null_; put '%end; '; put '%else %if &action=ARR or &action=OBJ %then %do; '; put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt '; - put ' ,jref=&fref,engine=PROCJSON,dbg=%str(&_debug) '; + put ' ,jref=&fref,engine=DATASTEP,dbg=%str(&_debug) '; put ' ) '; put '%end; '; put '%else %if &action=CLOSE %then %do; '; @@ -15376,7 +15382,7 @@ libname &libref clear; filename &fref1 clear; %mend;/** - @file mv_webout.sas + @file @brief Send data to/from the SAS Viya Job Execution Service @details This macro should be added to the start of each Job Execution Service, **immediately** followed by a call to: @@ -15388,7 +15394,7 @@ filename &fref1 clear; following syntax: data some datasets; * make some data ; - retain some columns; + retain some columns; run; %mv_webout(OPEN) @@ -15539,7 +15545,7 @@ filename &fref1 clear; %end; %else %if &action=ARR or &action=OBJ %then %do; %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt - ,jref=&fref,engine=PROCJSON,dbg=%str(&_debug) + ,jref=&fref,engine=DATASTEP,dbg=%str(&_debug) ) %end; %else %if &action=CLOSE %then %do; diff --git a/meta/mm_createwebservice.sas b/meta/mm_createwebservice.sas index 751f364..0642e2a 100644 --- a/meta/mm_createwebservice.sas +++ b/meta/mm_createwebservice.sas @@ -196,7 +196,7 @@ data _null_; put '%end; '; put ' '; put '%else %if &action=CLOSE %then %do; '; - put ' data _null_;file &jref encoding=''utf-8''; '; + put ' data _null_;file &jref encoding=''utf-8'' mod; '; put ' put "}"; '; put ' run; '; put '%end; '; diff --git a/viya/mv_createwebservice.sas b/viya/mv_createwebservice.sas index d531296..704e30c 100644 --- a/viya/mv_createwebservice.sas +++ b/viya/mv_createwebservice.sas @@ -335,7 +335,7 @@ data _null_; put '%end; '; put ' '; put '%else %if &action=CLOSE %then %do; '; - put ' data _null_;file &jref encoding=''utf-8''; '; + put ' data _null_;file &jref encoding=''utf-8'' mod; '; put ' put "}"; '; put ' run; '; put '%end; '; @@ -467,7 +467,7 @@ data _null_; put '%end; '; put '%else %if &action=ARR or &action=OBJ %then %do; '; put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt '; - put ' ,jref=&fref,engine=PROCJSON,dbg=%str(&_debug) '; + put ' ,jref=&fref,engine=DATASTEP,dbg=%str(&_debug) '; put ' ) '; put '%end; '; put '%else %if &action=CLOSE %then %do; '; From 1d6f04fd5647af57f1a1517c6df6fe681f290f92 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Thu, 8 Apr 2021 15:59:08 +0000 Subject: [PATCH 35/70] chore: adding macro related lint settings and sasjs as a recommended extension --- .gitpod.yml | 2 +- .sasjslint | 3 +++ .vscode/extensions.json | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .vscode/extensions.json diff --git a/.gitpod.yml b/.gitpod.yml index 46e5719..03e91d7 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -5,4 +5,4 @@ image: file: .gitpod.dockerfile vscode: extensions: - - sasjs.sasjs-for-vscode@1.7.2:R6y1nzpFh2P99BZg5FgH5g== + - sasjs.sasjs-for-vscode diff --git a/.sasjslint b/.sasjslint index d0e0091..3bc119b 100644 --- a/.sasjslint +++ b/.sasjslint @@ -2,6 +2,9 @@ "noTrailingSpaces": true, "noEncodedPasswords": true, "hasDoxygenHeader": true, + "hasMacroNameInMend": false, + "hasMacroParentheses": true, + "noNestedMacros": false, "noSpacesInFileNames": true, "maxLineLength": 135, "lowerCaseFileNames": true, diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..24cf286 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "sasjs.sasjs-for-vscode" + ] +} \ No newline at end of file From 00511c72c251fb2cb81664085d9527f59e8eee59 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Fri, 9 Apr 2021 11:46:37 +0000 Subject: [PATCH 36/70] fix: removing deprecated params from mm_createstp --- all.sas | 2 -- meta/mm_createstp.sas | 2 -- 2 files changed, 4 deletions(-) diff --git a/all.sas b/all.sas index 7acdb15..189eea4 100644 --- a/all.sas +++ b/all.sas @@ -7307,8 +7307,6 @@ run; */ %mm_updatestpsourcecode(stp=&tree/&stpname ,stpcode="&directory/&filename" - ,frefin=&frefin. - ,frefout=&frefout. ,mdebug=&mdebug ,minify=&minify) diff --git a/meta/mm_createstp.sas b/meta/mm_createstp.sas index cdbdeb3..53134b7 100755 --- a/meta/mm_createstp.sas +++ b/meta/mm_createstp.sas @@ -376,8 +376,6 @@ run; */ %mm_updatestpsourcecode(stp=&tree/&stpname ,stpcode="&directory/&filename" - ,frefin=&frefin. - ,frefout=&frefout. ,mdebug=&mdebug ,minify=&minify) From d6056b9397e608225b8160c076172cecb767a9e4 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sat, 10 Apr 2021 12:41:17 +0200 Subject: [PATCH 37/70] fix: adding mod statement to _webout to enable sas-side sasjs testing --- all.sas | 16 ++++++++-------- meta/mm_createwebservice.sas | 8 ++++---- meta/mm_webout.sas | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/all.sas b/all.sas index 189eea4..63c563a 100644 --- a/all.sas +++ b/all.sas @@ -7595,14 +7595,14 @@ data _null_; put ' i+1; '; put ' call symputx(''wt''!!left(i),name,''l''); '; put ' call symputx(''wtcnt'',i,''l''); '; - put ' data _null_; file &fref encoding=''utf-8''; '; + put ' data _null_; file &fref mod encoding=''utf-8''; '; put ' put ",""WORK"":{"; '; put ' %do i=1 %to &wtcnt; '; put ' %let wt=&&wt&i; '; put ' proc contents noprint data=&wt '; put ' out=_data_ (keep=name type length format:); '; put ' run;%let tempds=%scan(&syslast,2,.); '; - put ' data _null_; file &fref encoding=''utf-8''; '; + put ' data _null_; file &fref mod encoding=''utf-8''; '; put ' dsid=open("WORK.&wt",''is''); '; put ' nlobs=attrn(dsid,''NLOBS''); '; put ' nvars=attrn(dsid,''NVARS''); '; @@ -7613,10 +7613,10 @@ data _null_; put ' put '',"nvars":'' nvars; '; put ' %mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=DATASTEP) '; put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=DATASTEP) '; - put ' data _null_; file &fref encoding=''utf-8''; '; + put ' data _null_; file &fref mod encoding=''utf-8''; '; put ' put "}"; '; put ' %end; '; - put ' data _null_; file &fref encoding=''utf-8''; '; + put ' data _null_; file &fref mod encoding=''utf-8''; '; put ' put "}"; '; put ' run; '; put ' %end; '; @@ -11064,14 +11064,14 @@ run; i+1; call symputx('wt'!!left(i),name,'l'); call symputx('wtcnt',i,'l'); - data _null_; file &fref encoding='utf-8'; + data _null_; file &fref mod encoding='utf-8'; put ",""WORK"":{"; %do i=1 %to &wtcnt; %let wt=&&wt&i; proc contents noprint data=&wt out=_data_ (keep=name type length format:); run;%let tempds=%scan(&syslast,2,.); - data _null_; file &fref encoding='utf-8'; + data _null_; file &fref mod encoding='utf-8'; dsid=open("WORK.&wt",'is'); nlobs=attrn(dsid,'NLOBS'); nvars=attrn(dsid,'NVARS'); @@ -11082,10 +11082,10 @@ run; put ',"nvars":' nvars; %mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=DATASTEP) %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=DATASTEP) - data _null_; file &fref encoding='utf-8'; + data _null_; file &fref mod encoding='utf-8'; put "}"; %end; - data _null_; file &fref encoding='utf-8'; + data _null_; file &fref mod encoding='utf-8'; put "}"; run; %end; diff --git a/meta/mm_createwebservice.sas b/meta/mm_createwebservice.sas index 0642e2a..858ae4b 100644 --- a/meta/mm_createwebservice.sas +++ b/meta/mm_createwebservice.sas @@ -277,14 +277,14 @@ data _null_; put ' i+1; '; put ' call symputx(''wt''!!left(i),name,''l''); '; put ' call symputx(''wtcnt'',i,''l''); '; - put ' data _null_; file &fref encoding=''utf-8''; '; + put ' data _null_; file &fref mod encoding=''utf-8''; '; put ' put ",""WORK"":{"; '; put ' %do i=1 %to &wtcnt; '; put ' %let wt=&&wt&i; '; put ' proc contents noprint data=&wt '; put ' out=_data_ (keep=name type length format:); '; put ' run;%let tempds=%scan(&syslast,2,.); '; - put ' data _null_; file &fref encoding=''utf-8''; '; + put ' data _null_; file &fref mod encoding=''utf-8''; '; put ' dsid=open("WORK.&wt",''is''); '; put ' nlobs=attrn(dsid,''NLOBS''); '; put ' nvars=attrn(dsid,''NVARS''); '; @@ -295,10 +295,10 @@ data _null_; put ' put '',"nvars":'' nvars; '; put ' %mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=DATASTEP) '; put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=DATASTEP) '; - put ' data _null_; file &fref encoding=''utf-8''; '; + put ' data _null_; file &fref mod encoding=''utf-8''; '; put ' put "}"; '; put ' %end; '; - put ' data _null_; file &fref encoding=''utf-8''; '; + put ' data _null_; file &fref mod encoding=''utf-8''; '; put ' put "}"; '; put ' run; '; put ' %end; '; diff --git a/meta/mm_webout.sas b/meta/mm_webout.sas index 6c9fe26..8c71e09 100644 --- a/meta/mm_webout.sas +++ b/meta/mm_webout.sas @@ -108,14 +108,14 @@ i+1; call symputx('wt'!!left(i),name,'l'); call symputx('wtcnt',i,'l'); - data _null_; file &fref encoding='utf-8'; + data _null_; file &fref mod encoding='utf-8'; put ",""WORK"":{"; %do i=1 %to &wtcnt; %let wt=&&wt&i; proc contents noprint data=&wt out=_data_ (keep=name type length format:); run;%let tempds=%scan(&syslast,2,.); - data _null_; file &fref encoding='utf-8'; + data _null_; file &fref mod encoding='utf-8'; dsid=open("WORK.&wt",'is'); nlobs=attrn(dsid,'NLOBS'); nvars=attrn(dsid,'NVARS'); @@ -126,10 +126,10 @@ put ',"nvars":' nvars; %mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=DATASTEP) %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=DATASTEP) - data _null_; file &fref encoding='utf-8'; + data _null_; file &fref mod encoding='utf-8'; put "}"; %end; - data _null_; file &fref encoding='utf-8'; + data _null_; file &fref mod encoding='utf-8'; put "}"; run; %end; From ba1272aaf7724ba0fde21fbe084f14ac85ef2dae Mon Sep 17 00:00:00 2001 From: allanbowe Date: Sat, 17 Apr 2021 00:11:06 +0200 Subject: [PATCH 38/70] fix: updating mv_createwebservice to support 0x01 hex characters, adding a test (and test scaffolding) as part of this. The test scaffolding will be updated once goes live - for now it is being deployed as a service. --- .gitignore | 5 ++++- sasjs/sasjsconfig.json | 25 +++++++++++++++++++++++-- tests/testinit.sas | 8 ++++++++ tests/viya/mv_createwebservice.test.sas | 24 ++++++++++++++++++++++++ viya/mv_createwebservice.sas | 18 ++++++++++++++---- 5 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 tests/testinit.sas create mode 100644 tests/viya/mv_createwebservice.test.sas diff --git a/.gitignore b/.gitignore index 862fe0a..9411085 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ sasjsbuild/ **\ ** # ignore the mc_* files - containing macros for individual libraries -mc_* \ No newline at end of file +mc_* + +# ignore .env files as they can contain sasjs access tokens +*.env* \ No newline at end of file diff --git a/sasjs/sasjsconfig.json b/sasjs/sasjsconfig.json index cea7e36..192034e 100644 --- a/sasjs/sasjsconfig.json +++ b/sasjs/sasjsconfig.json @@ -1,7 +1,7 @@ { "$schema": "https://cli.sasjs.io/sasjsconfig-schema.json", "macroFolders": ["base", "meta", "metax", "viya", "lua"], - "docConfig":{ + "docConfig": { "displayMacroCore": false, "enableLineage": false, "doxyContent": { @@ -9,5 +9,26 @@ "logo": "Macro_core_website_1.png", "readMe": "../../README.md" } - } + }, + "serviceConfig": { + "initProgram": "tests/testinit.sas" + }, + "defaultTarget": "viya", + "targets": [ + { + "name": "viya", + "serverUrl": "https://sas.analytium.co.uk", + "serverType": "SASVIYA", + "appLoc": "/Public/temp/macrocore", + "serviceConfig": { + "serviceFolders": ["tests/viya"], + "macroVars": { + "mcTestAppLoc": "/Public/temp/macrocore" + } + }, + "deployConfig": { + "deployServicePack": true + } + } + ] } diff --git a/tests/testinit.sas b/tests/testinit.sas new file mode 100644 index 0000000..4daa961 --- /dev/null +++ b/tests/testinit.sas @@ -0,0 +1,8 @@ +/** + @file + @brief init file for tests + +**/ + +/* location in metadata or SAS Drive for temporary files */ +%let mcTestAppLoc=/Public/temp/test; \ No newline at end of file diff --git a/tests/viya/mv_createwebservice.test.sas b/tests/viya/mv_createwebservice.test.sas new file mode 100644 index 0000000..4abee82 --- /dev/null +++ b/tests/viya/mv_createwebservice.test.sas @@ -0,0 +1,24 @@ +/** + @file + @brief Testing mv_createwebservice macro + +

SAS Macros

+ @li mv_createwebservice.sas + +**/ + +/** + * Test Case 1 + * Send special char in a service + */ + +filename testref temp; +data _null_; + file testref; + put '01'x; +run; +%mv_createwebservice( + path=&mcTestAppLoc/tests/macros, + code=testref, + name=mv_createwebservice +) \ No newline at end of file diff --git a/viya/mv_createwebservice.sas b/viya/mv_createwebservice.sas index 704e30c..770dec4 100644 --- a/viya/mv_createwebservice.sas +++ b/viya/mv_createwebservice.sas @@ -46,9 +46,10 @@ needs to be attached to the beginning of the service @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 service in that location + @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 service in that + location @param adapter= the macro uses the sasjs adapter by default. To use another adapter, add a (different) fileref here. @param contextname= Choose a specific context on which to run the Job. Leave @@ -145,7 +146,8 @@ libname &libref1 JSON fileref=&fname1; data _null_; set &libref1..links; - if rel='members' then call symputx('membercheck',quote("&base_uri"!!trim(href)),'l'); + 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_; @@ -592,6 +594,14 @@ run; rc =fput(fileid,'\');rc =fwrite(fileid); rc =fput(fileid,'\');rc =fwrite(fileid); end; + else if rec='01'x then do; /* Unprintable */ + rc =fput(fileid,'\');rc =fwrite(fileid); + rc =fput(fileid,'u');rc =fwrite(fileid); + rc =fput(fileid,'0');rc =fwrite(fileid); + rc =fput(fileid,'0');rc =fwrite(fileid); + rc =fput(fileid,'0');rc =fwrite(fileid); + rc =fput(fileid,'1');rc =fwrite(fileid); + end; else do; rc =fput(fileid,rec); rc =fwrite(fileid); From ecdce86287fd6fc6a60a41630002c531566a9aef Mon Sep 17 00:00:00 2001 From: allanbowe Date: Sat, 17 Apr 2021 00:11:33 +0200 Subject: [PATCH 39/70] fix: adding all.sas also --- all.sas | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/all.sas b/all.sas index 63c563a..b8d1524 100644 --- a/all.sas +++ b/all.sas @@ -5374,6 +5374,60 @@ alter table &libds modify &var char(&len); %mend; /** + @file + @brief Validates a filter clause + @details Validates a filter to avoid SQL injection. Works by removing string + literals, then ensuring that none of the following characters remain: &,%,; + + + + %mp_setkeyvalue(someindex,22,type=N) + %mp_setkeyvalue(somenewindex,somevalue) + +

SAS Macros

+ @li mf_existds.sas + + @param key Provide a key on which to perform the lookup + @param value Provide a value + @param type= either C or N will populate valc and valn respectively. C is + default. + @param libds= define the target table to hold the parameters + + @version 9.2 + @author Allan Bowe + @source https://github.com/sasjs/core + +**/ + +%macro mp_setkeyvalue(key,value,type=C,libds=work.mp_setkeyvalue +)/*/STORE SOURCE*/; + + %if not (%mf_existds(&libds)) %then %do; + data &libds (index=(key/unique)); + length key $64 valc $2048 valn 8 type $1; + call missing(of _all_); + stop; + run; + %end; + + proc sql; + delete from &libds + where key=symget('key'); + insert into &libds + set key=symget('key') + %if &type=C %then %do; + ,valc=symget('value') + ,type='C' + %end; + %else %do; + ,valn=symgetn('value') + ,type='N' + %end; + ; + + quit; + +%mend;/** @file @brief Creates a zip file @details For DIRECTORY usage, will ignore subfolders. For DATASET usage, @@ -11752,9 +11806,10 @@ run; needs to be attached to the beginning of the service @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 service in that location + @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 service in that + location @param adapter= the macro uses the sasjs adapter by default. To use another adapter, add a (different) fileref here. @param contextname= Choose a specific context on which to run the Job. Leave @@ -11851,7 +11906,8 @@ libname &libref1 JSON fileref=&fname1; data _null_; set &libref1..links; - if rel='members' then call symputx('membercheck',quote("&base_uri"!!trim(href)),'l'); + 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_; @@ -12298,6 +12354,14 @@ run; rc =fput(fileid,'\');rc =fwrite(fileid); rc =fput(fileid,'\');rc =fwrite(fileid); end; + else if rec='01'x then do; /* Unprintable */ + rc =fput(fileid,'\');rc =fwrite(fileid); + rc =fput(fileid,'u');rc =fwrite(fileid); + rc =fput(fileid,'0');rc =fwrite(fileid); + rc =fput(fileid,'0');rc =fwrite(fileid); + rc =fput(fileid,'0');rc =fwrite(fileid); + rc =fput(fileid,'1');rc =fwrite(fileid); + end; else do; rc =fput(fileid,rec); rc =fwrite(fileid); From af71a5e53b2b71e2b203ef7336e31e2d6f018a46 Mon Sep 17 00:00:00 2001 From: allanbowe Date: Fri, 23 Apr 2021 00:33:11 +0200 Subject: [PATCH 40/70] feat: new macro for hashing a table (mp_hashdataset) --- all.sas | 127 +++++++++++++++++++++++----------------- base/mp_hashdataset.sas | 74 +++++++++++++++++++++++ 2 files changed, 147 insertions(+), 54 deletions(-) create mode 100644 base/mp_hashdataset.sas diff --git a/all.sas b/all.sas index b8d1524..c95541a 100644 --- a/all.sas +++ b/all.sas @@ -3913,6 +3913,79 @@ create table &outds (rename=( %return; %end; +%mend;/** + @file + @brief Returns a unique hash for a dataset + @details Ignores metadata attributes, used only to hash values. Compared + datasets must be in the same order. + + %mp_hashdataset(sashelp.class,outds=myhash); + + data _null_; + set work.myhash; + put hashkey=; + run; + + +

SAS Macros

+ @li mf_getattrn.sas + @li mf_getuniquename.sas + @li mf_getvarlist.sas + @li mf_getvartype.sas + + @param [in] libds dataset to hash + @param [out] outds= (work.mf_hashdataset) The output dataset to create. This + will contain one column (hashkey) with one observation (a hex32. + representation of the input hash) + |hashkey:$32.| + |---| + |28ABC74ABFC45F50794237BA5566E6CA| + + @version 9.2 + @author Allan Bowe +**/ + +%macro mp_hashdataset( + libds, + outds= +)/*/STORE SOURCE*/; + %if %mf_getattrn(&libds,NLOBS)=0 %then %do; + %put %str(WARN)ING: Dataset &libds is empty;, or is not a dataset; + %end; + %else %if %mf_getattrn(&libds,NLOBS)<0 %then %do; + %put %str(ERR)OR: Dataset &libds is not a dataset; + %end; + %else %do; + %local keyvar /* roll up the md5 */ + prevkeyvar /* retain prev record md5 */ + lastvar /* last var in input ds */ + varlist var i; + /* avoid naming conflict for hash key vars */ + %let keyvar=%mf_getuniquename(); + %let prevkeyvar=%mf_getuniquename(); + %let lastvar=%mf_getuniquename(); + %let varlist=%mf_getvarlist(&libds); + data &outds(rename=(&keyvar=hashkey) keep=&keyvar); + length &prevkeyvar &keyvar $32; + retain &prevkeyvar; + set &libds end=&lastvar; + /* hash should include previous row */ + if _n_>1 then &keyvar=put(md5(&prevkeyvar + /* loop every column, hashing every individual value */ + %do i=1 %to %sysfunc(countw(&varlist)); + %let var=%scan(&varlist,&i,%str( )); + %if %mf_getvartype(&libds,&var)=C %then %do; + !!put(md5(trim(&var)),$hex32.) + %end; + %else %do; + !!put(md5(trim(put(&var*1,binary64.))),$hex32.) + %end; + %end; + ),$hex32.); + &prevkeyvar=&keyvar; + if &lastvar then output; + run; + %end; %mend;/** @file mp_jsonout.sas @brief Writes JSON in SASjs format to a fileref @@ -5374,60 +5447,6 @@ alter table &libds modify &var char(&len); %mend; /** - @file - @brief Validates a filter clause - @details Validates a filter to avoid SQL injection. Works by removing string - literals, then ensuring that none of the following characters remain: &,%,; - - - - %mp_setkeyvalue(someindex,22,type=N) - %mp_setkeyvalue(somenewindex,somevalue) - -

SAS Macros

- @li mf_existds.sas - - @param key Provide a key on which to perform the lookup - @param value Provide a value - @param type= either C or N will populate valc and valn respectively. C is - default. - @param libds= define the target table to hold the parameters - - @version 9.2 - @author Allan Bowe - @source https://github.com/sasjs/core - -**/ - -%macro mp_setkeyvalue(key,value,type=C,libds=work.mp_setkeyvalue -)/*/STORE SOURCE*/; - - %if not (%mf_existds(&libds)) %then %do; - data &libds (index=(key/unique)); - length key $64 valc $2048 valn 8 type $1; - call missing(of _all_); - stop; - run; - %end; - - proc sql; - delete from &libds - where key=symget('key'); - insert into &libds - set key=symget('key') - %if &type=C %then %do; - ,valc=symget('value') - ,type='C' - %end; - %else %do; - ,valn=symgetn('value') - ,type='N' - %end; - ; - - quit; - -%mend;/** @file @brief Creates a zip file @details For DIRECTORY usage, will ignore subfolders. For DATASET usage, diff --git a/base/mp_hashdataset.sas b/base/mp_hashdataset.sas new file mode 100644 index 0000000..8aa4be4 --- /dev/null +++ b/base/mp_hashdataset.sas @@ -0,0 +1,74 @@ +/** + @file + @brief Returns a unique hash for a dataset + @details Ignores metadata attributes, used only to hash values. Compared + datasets must be in the same order. + + %mp_hashdataset(sashelp.class,outds=myhash); + + data _null_; + set work.myhash; + put hashkey=; + run; + + +

SAS Macros

+ @li mf_getattrn.sas + @li mf_getuniquename.sas + @li mf_getvarlist.sas + @li mf_getvartype.sas + + @param [in] libds dataset to hash + @param [out] outds= (work.mf_hashdataset) The output dataset to create. This + will contain one column (hashkey) with one observation (a hex32. + representation of the input hash) + |hashkey:$32.| + |---| + |28ABC74ABFC45F50794237BA5566E6CA| + + @version 9.2 + @author Allan Bowe +**/ + +%macro mp_hashdataset( + libds, + outds= +)/*/STORE SOURCE*/; + %if %mf_getattrn(&libds,NLOBS)=0 %then %do; + %put %str(WARN)ING: Dataset &libds is empty;, or is not a dataset; + %end; + %else %if %mf_getattrn(&libds,NLOBS)<0 %then %do; + %put %str(ERR)OR: Dataset &libds is not a dataset; + %end; + %else %do; + %local keyvar /* roll up the md5 */ + prevkeyvar /* retain prev record md5 */ + lastvar /* last var in input ds */ + varlist var i; + /* avoid naming conflict for hash key vars */ + %let keyvar=%mf_getuniquename(); + %let prevkeyvar=%mf_getuniquename(); + %let lastvar=%mf_getuniquename(); + %let varlist=%mf_getvarlist(&libds); + data &outds(rename=(&keyvar=hashkey) keep=&keyvar); + length &prevkeyvar &keyvar $32; + retain &prevkeyvar; + set &libds end=&lastvar; + /* hash should include previous row */ + if _n_>1 then &keyvar=put(md5(&prevkeyvar + /* loop every column, hashing every individual value */ + %do i=1 %to %sysfunc(countw(&varlist)); + %let var=%scan(&varlist,&i,%str( )); + %if %mf_getvartype(&libds,&var)=C %then %do; + !!put(md5(trim(&var)),$hex32.) + %end; + %else %do; + !!put(md5(trim(put(&var*1,binary64.))),$hex32.) + %end; + %end; + ),$hex32.); + &prevkeyvar=&keyvar; + if &lastvar then output; + run; + %end; +%mend; \ No newline at end of file From 053290c7df55886a71b8e4051ed7061f2ecf7273 Mon Sep 17 00:00:00 2001 From: allanbowe Date: Fri, 23 Apr 2021 07:56:33 +0200 Subject: [PATCH 41/70] chore: updating header of mp_hashdataset --- all.sas | 3 ++- base/mp_hashdataset.sas | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/all.sas b/all.sas index c95541a..ca9f62c 100644 --- a/all.sas +++ b/all.sas @@ -3919,13 +3919,14 @@ create table &outds (rename=( @details Ignores metadata attributes, used only to hash values. Compared datasets must be in the same order. - %mp_hashdataset(sashelp.class,outds=myhash); + %mp_hashdataset(sashelp.class,outds=myhash) data _null_; set work.myhash; put hashkey=; run; + ![sas md5 hash dataset log results](https://i.imgur.com/MqF98vk.png)

SAS Macros

@li mf_getattrn.sas diff --git a/base/mp_hashdataset.sas b/base/mp_hashdataset.sas index 8aa4be4..264cfc2 100644 --- a/base/mp_hashdataset.sas +++ b/base/mp_hashdataset.sas @@ -4,13 +4,14 @@ @details Ignores metadata attributes, used only to hash values. Compared datasets must be in the same order. - %mp_hashdataset(sashelp.class,outds=myhash); + %mp_hashdataset(sashelp.class,outds=myhash) data _null_; set work.myhash; put hashkey=; run; + ![sas md5 hash dataset log results](https://i.imgur.com/MqF98vk.png)

SAS Macros

@li mf_getattrn.sas From 66ff1de7a94880d595be83cd6b9884d20e504de7 Mon Sep 17 00:00:00 2001 From: allanbowe Date: Fri, 23 Apr 2021 23:06:37 +0200 Subject: [PATCH 42/70] fix: reducing logging --- all.sas | 20 ++++++++++---------- viya/mv_createfolder.sas | 12 +++++++----- viya/mv_createjob.sas | 1 - viya/mv_createwebservice.sas | 1 - viya/mv_deletejes.sas | 2 +- viya/mv_deleteviyafolder.sas | 2 +- viya/mv_getgroupmembers.sas | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/all.sas b/all.sas index ca9f62c..abb49dd 100644 --- a/all.sas +++ b/all.sas @@ -11331,8 +11331,8 @@ run; @param path= The full path of the folder to be created @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 grant_type= (authorization_code) Valid values are "password" or + "authorization_code" (unquoted). @version VIYA V.03.04 @@ -11361,7 +11361,6 @@ run; %let &access_token_var=; %end; -%put &sysmacroname: grant_type=&grant_type; %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password and &grant_type ne sas_services ) @@ -11407,12 +11406,15 @@ options noquotelenmax; %local libref1; %let libref1=%mf_getuniquelibref(); libname &libref1 JSON fileref=&fname1; - %mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 404) + %mp_abort( + iftrue=( + &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 404 + ) ,mac=&sysmacroname ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) ) %if &SYS_PROCHTTP_STATUS_CODE=200 %then %do; - %put &sysmacroname &newpath exists so grab the follow on link ; + %*put &sysmacroname &newpath exists so grab the follow on link ; data _null_; set &libref1..links; if rel='createChild' then @@ -11544,7 +11546,6 @@ https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5p %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 @@ -11863,7 +11864,6 @@ https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5p %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 @@ -12647,7 +12647,7 @@ libname &libref1a clear; %let oauth_bearer=oauth_bearer=sas_services; %let &access_token_var=; %end; -%put &sysmacroname: grant_type=&grant_type; + %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password and &grant_type ne sas_services ) @@ -12789,7 +12789,7 @@ libname &libref1a clear; %let oauth_bearer=oauth_bearer=sas_services; %let &access_token_var=; %end; -%put &sysmacroname: grant_type=&grant_type; + %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password and &grant_type ne sas_services ) @@ -13215,7 +13215,7 @@ libname &libref1 clear; %let oauth_bearer=oauth_bearer=sas_services; %let &access_token_var=; %end; -%put &sysmacroname: grant_type=&grant_type; + %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password and &grant_type ne sas_services ) diff --git a/viya/mv_createfolder.sas b/viya/mv_createfolder.sas index 582ac66..2af60f0 100644 --- a/viya/mv_createfolder.sas +++ b/viya/mv_createfolder.sas @@ -9,8 +9,8 @@ @param path= The full path of the folder to be created @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 grant_type= (authorization_code) Valid values are "password" or + "authorization_code" (unquoted). @version VIYA V.03.04 @@ -39,7 +39,6 @@ %let &access_token_var=; %end; -%put &sysmacroname: grant_type=&grant_type; %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password and &grant_type ne sas_services ) @@ -85,12 +84,15 @@ options noquotelenmax; %local libref1; %let libref1=%mf_getuniquelibref(); libname &libref1 JSON fileref=&fname1; - %mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 404) + %mp_abort( + iftrue=( + &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 404 + ) ,mac=&sysmacroname ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) ) %if &SYS_PROCHTTP_STATUS_CODE=200 %then %do; - %put &sysmacroname &newpath exists so grab the follow on link ; + %*put &sysmacroname &newpath exists so grab the follow on link ; data _null_; set &libref1..links; if rel='createChild' then diff --git a/viya/mv_createjob.sas b/viya/mv_createjob.sas index 2411526..4186cff 100644 --- a/viya/mv_createjob.sas +++ b/viya/mv_createjob.sas @@ -72,7 +72,6 @@ https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5p %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 diff --git a/viya/mv_createwebservice.sas b/viya/mv_createwebservice.sas index 770dec4..7e5f712 100644 --- a/viya/mv_createwebservice.sas +++ b/viya/mv_createwebservice.sas @@ -83,7 +83,6 @@ https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5p %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 diff --git a/viya/mv_deletejes.sas b/viya/mv_deletejes.sas index 3759d8f..a4a5138 100644 --- a/viya/mv_deletejes.sas +++ b/viya/mv_deletejes.sas @@ -44,7 +44,7 @@ %let oauth_bearer=oauth_bearer=sas_services; %let &access_token_var=; %end; -%put &sysmacroname: grant_type=&grant_type; + %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password and &grant_type ne sas_services ) diff --git a/viya/mv_deleteviyafolder.sas b/viya/mv_deleteviyafolder.sas index 2b5e640..e700b68 100644 --- a/viya/mv_deleteviyafolder.sas +++ b/viya/mv_deleteviyafolder.sas @@ -39,7 +39,7 @@ %let oauth_bearer=oauth_bearer=sas_services; %let &access_token_var=; %end; -%put &sysmacroname: grant_type=&grant_type; + %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password and &grant_type ne sas_services ) diff --git a/viya/mv_getgroupmembers.sas b/viya/mv_getgroupmembers.sas index d715b8c..302319e 100644 --- a/viya/mv_getgroupmembers.sas +++ b/viya/mv_getgroupmembers.sas @@ -54,7 +54,7 @@ %let oauth_bearer=oauth_bearer=sas_services; %let &access_token_var=; %end; -%put &sysmacroname: grant_type=&grant_type; + %mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password and &grant_type ne sas_services ) From 029169ac8048c1f600474aee185c70020c3166c2 Mon Sep 17 00:00:00 2001 From: Beast Date: Sat, 1 May 2021 15:32:45 +0300 Subject: [PATCH 43/70] feat: adding mf_getxengine macro for determining the engine of a sas fileref --- base/mf_getengine.sas | 3 +++ base/mf_getxengine.sas | 43 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 base/mf_getxengine.sas diff --git a/base/mf_getengine.sas b/base/mf_getengine.sas index a45b801..638154f 100755 --- a/base/mf_getengine.sas +++ b/base/mf_getengine.sas @@ -22,6 +22,9 @@ @version 9.2 @author Allan Bowe +

Related Macros

+ @li mf_getxengine.sas + **/ /** @cond */ diff --git a/base/mf_getxengine.sas b/base/mf_getxengine.sas new file mode 100644 index 0000000..7ad34ce --- /dev/null +++ b/base/mf_getxengine.sas @@ -0,0 +1,43 @@ +/** + @file + @brief Returns the engine type of a SAS fileref + @details Queries sashelp.vextfl to get the xengine value. + Usage: + + filename feng temp; + %put %mf_getxengine(feng); + + returns: + > TEMP + + @param fref The fileref to check + + @returns The XENGINE value in sashelp.vextfl or 0 if not found. + + @version 9.2 + @author Allan Bowe + +

Related Macros

+ @li mf_getengine.sas + +**/ + +%macro mf_getxengine(fref +)/*/STORE SOURCE*/; + %local dsid engnum rc engine; + + %let dsid=%sysfunc( + open(sashelp.vextfl(where=(fileref="%upcase(&fref)")),i) + ); + %if (&dsid ^= 0) %then %do; + %let engnum=%sysfunc(varnum(&dsid,XENGINE)); + %let rc=%sysfunc(fetch(&dsid)); + %let engine=%sysfunc(getvarc(&dsid,&engnum)); + %* put &fref. ENGINE is &engine.; + %let rc= %sysfunc(close(&dsid)); + %end; + %else %let engine=0; + + &engine + +%mend; From 328f8c260be21cc56da82254b87ea1e354d05185 Mon Sep 17 00:00:00 2001 From: Beast Date: Sat, 1 May 2021 15:34:30 +0300 Subject: [PATCH 44/70] chore: updating all.sas --- all.sas | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/all.sas b/all.sas index abb49dd..38ef21b 100644 --- a/all.sas +++ b/all.sas @@ -442,6 +442,9 @@ options noquotelenmax; @version 9.2 @author Allan Bowe +

Related Macros

+ @li mf_getxengine.sas + **/ /** @cond */ @@ -1189,6 +1192,49 @@ Usage: /* Return variable type */ &vtype %mend;/** + @file + @brief Returns the engine type of a SAS fileref + @details Queries sashelp.vextfl to get the xengine value. + Usage: + + filename feng temp; + %put %mf_getxengine(feng); + + returns: + > TEMP + + @param fref The fileref to check + + @returns The XENGINE value in sashelp.vextfl or 0 if not found. + + @version 9.2 + @author Allan Bowe + +

Related Macros

+ @li mf_getengine.sas + +**/ + +%macro mf_getxengine(fref +)/*/STORE SOURCE*/; + %local dsid engnum rc engine; + + %let dsid=%sysfunc( + open(sashelp.vextfl(where=(fileref="%upcase(&fref)")),i) + ); + %if (&dsid ^= 0) %then %do; + %let engnum=%sysfunc(varnum(&dsid,XENGINE)); + %let rc=%sysfunc(fetch(&dsid)); + %let engine=%sysfunc(getvarc(&dsid,&engnum)); + %* put &fref. ENGINE is &engine.; + %let rc= %sysfunc(close(&dsid)); + %end; + %else %let engine=0; + + &engine + +%mend; +/** @file mf_isblank.sas @brief Checks whether a macro variable is empty (blank) @details Simply performs: From fdd566e8ce1eedc948f785c9e2c134200054dc28 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sat, 1 May 2021 16:27:41 +0300 Subject: [PATCH 45/70] fix: setting server headers only if STREAM mode enabled to avoid 'Function is only valid for filerefs using the CACHE access method.' error when testing STPs from Studio. Also removing proc json as it cannot handle invalid characters. --- all.sas | 46 +++++++++++++++++++----------------- meta/mm_createwebservice.sas | 23 +++++++++--------- meta/mm_webout.sas | 23 +++++++++--------- 3 files changed, 48 insertions(+), 44 deletions(-) diff --git a/all.sas b/all.sas index 38ef21b..2c49690 100644 --- a/all.sas +++ b/all.sas @@ -7675,8 +7675,16 @@ data _null_; put '%else %if &action=OPEN %then %do; '; put ' /* fix encoding */ '; put ' OPTIONS NOBOMFILE; '; + put ' '; + put ' /** '; + put ' * check engine type to avoid the below err message: '; + put ' * > Function is only valid for filerefs using the CACHE access method. '; + put ' */ '; put ' data _null_; '; - put ' rc = stpsrv_header(''Content-type'',"text/html; encoding=utf-8"); '; + put ' set sashelp.vextfl(where=(fileref="_WEBOUT")); '; + put ' if xengine=''STREAM'' then do; '; + put ' rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8"); '; + put ' end; '; put ' run; '; put ' '; put ' /* setup json */ '; @@ -7690,16 +7698,9 @@ data _null_; put '%end; '; put ' '; put '%else %if &action=ARR or &action=OBJ %then %do; '; - put ' %if &sysver=9.4 %then %do; '; - put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt '; - put ' ,engine=PROCJSON,dbg=%str(&_debug) '; - put ' ) '; - put ' %end; '; - put ' %else %do; '; - put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt '; - put ' ,engine=DATASTEP,dbg=%str(&_debug) '; - put ' ) '; - put ' %end; '; + put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt '; + put ' ,engine=DATASTEP,dbg=%str(&_debug) '; + put ' ) '; put '%end; '; put '%else %if &action=CLOSE %then %do; '; put ' %if %str(&_debug) ge 131 %then %do; '; @@ -11144,8 +11145,16 @@ run; %else %if &action=OPEN %then %do; /* fix encoding */ OPTIONS NOBOMFILE; + + /** + * check engine type to avoid the below err message: + * > Function is only valid for filerefs using the CACHE access method. + */ data _null_; - rc = stpsrv_header('Content-type',"text/html; encoding=utf-8"); + set sashelp.vextfl(where=(fileref="_WEBOUT")); + if xengine='STREAM' then do; + rc=stpsrv_header('Content-type',"text/html; encoding=utf-8"); + end; run; /* setup json */ @@ -11159,16 +11168,9 @@ run; %end; %else %if &action=ARR or &action=OBJ %then %do; - %if &sysver=9.4 %then %do; - %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt - ,engine=PROCJSON,dbg=%str(&_debug) - ) - %end; - %else %do; - %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt - ,engine=DATASTEP,dbg=%str(&_debug) - ) - %end; + %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt + ,engine=DATASTEP,dbg=%str(&_debug) + ) %end; %else %if &action=CLOSE %then %do; %if %str(&_debug) ge 131 %then %do; diff --git a/meta/mm_createwebservice.sas b/meta/mm_createwebservice.sas index 858ae4b..9bdfb39 100644 --- a/meta/mm_createwebservice.sas +++ b/meta/mm_createwebservice.sas @@ -237,8 +237,16 @@ data _null_; put '%else %if &action=OPEN %then %do; '; put ' /* fix encoding */ '; put ' OPTIONS NOBOMFILE; '; + put ' '; + put ' /** '; + put ' * check engine type to avoid the below err message: '; + put ' * > Function is only valid for filerefs using the CACHE access method. '; + put ' */ '; put ' data _null_; '; - put ' rc = stpsrv_header(''Content-type'',"text/html; encoding=utf-8"); '; + put ' set sashelp.vextfl(where=(fileref="_WEBOUT")); '; + put ' if xengine=''STREAM'' then do; '; + put ' rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8"); '; + put ' end; '; put ' run; '; put ' '; put ' /* setup json */ '; @@ -252,16 +260,9 @@ data _null_; put '%end; '; put ' '; put '%else %if &action=ARR or &action=OBJ %then %do; '; - put ' %if &sysver=9.4 %then %do; '; - put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt '; - put ' ,engine=PROCJSON,dbg=%str(&_debug) '; - put ' ) '; - put ' %end; '; - put ' %else %do; '; - put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt '; - put ' ,engine=DATASTEP,dbg=%str(&_debug) '; - put ' ) '; - put ' %end; '; + put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt '; + put ' ,engine=DATASTEP,dbg=%str(&_debug) '; + put ' ) '; put '%end; '; put '%else %if &action=CLOSE %then %do; '; put ' %if %str(&_debug) ge 131 %then %do; '; diff --git a/meta/mm_webout.sas b/meta/mm_webout.sas index 8c71e09..a108803 100644 --- a/meta/mm_webout.sas +++ b/meta/mm_webout.sas @@ -68,8 +68,16 @@ %else %if &action=OPEN %then %do; /* fix encoding */ OPTIONS NOBOMFILE; + + /** + * check engine type to avoid the below err message: + * > Function is only valid for filerefs using the CACHE access method. + */ data _null_; - rc = stpsrv_header('Content-type',"text/html; encoding=utf-8"); + set sashelp.vextfl(where=(fileref="_WEBOUT")); + if xengine='STREAM' then do; + rc=stpsrv_header('Content-type',"text/html; encoding=utf-8"); + end; run; /* setup json */ @@ -83,16 +91,9 @@ %end; %else %if &action=ARR or &action=OBJ %then %do; - %if &sysver=9.4 %then %do; - %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt - ,engine=PROCJSON,dbg=%str(&_debug) - ) - %end; - %else %do; - %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt - ,engine=DATASTEP,dbg=%str(&_debug) - ) - %end; + %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt + ,engine=DATASTEP,dbg=%str(&_debug) + ) %end; %else %if &action=CLOSE %then %do; %if %str(&_debug) ge 131 %then %do; From ff82f7d75ccda9447adb6be3c371d842bb715bcc Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sat, 1 May 2021 20:33:45 +0300 Subject: [PATCH 46/70] chore: header update in mf_getvarlist --- all.sas | 9 ++++++--- base/mf_getvarlist.sas | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/all.sas b/all.sas index 2c49690..a7c2b12 100644 --- a/all.sas +++ b/all.sas @@ -1040,14 +1040,17 @@ options noquotelenmax; returns: > List of Variables=Name Sex Age Height Weight + For a seperated list of column values: + %put %mf_getvarlist(sashelp.class,dlm=%str(,),quote=double); returns: > "Name","Sex","Age","Height","Weight" - @param libds Two part dataset (or view) reference. - @param dlm= provide a delimiter (eg comma or space) to separate the vars - @param quote= use either DOUBLE or SINGLE to quote the results + @param [in] libds Two part dataset (or view) reference. + @param [in] dlm= ( ) Provide a delimiter (eg comma or space) to separate the + variables + @param [in] quote= (none) use either DOUBLE or SINGLE to quote the results @version 9.2 @author Allan Bowe diff --git a/base/mf_getvarlist.sas b/base/mf_getvarlist.sas index 9b7bec7..cad5bf3 100755 --- a/base/mf_getvarlist.sas +++ b/base/mf_getvarlist.sas @@ -10,14 +10,17 @@ returns: > List of Variables=Name Sex Age Height Weight + For a seperated list of column values: + %put %mf_getvarlist(sashelp.class,dlm=%str(,),quote=double); returns: > "Name","Sex","Age","Height","Weight" - @param libds Two part dataset (or view) reference. - @param dlm= provide a delimiter (eg comma or space) to separate the vars - @param quote= use either DOUBLE or SINGLE to quote the results + @param [in] libds Two part dataset (or view) reference. + @param [in] dlm= ( ) Provide a delimiter (eg comma or space) to separate the + variables + @param [in] quote= (none) use either DOUBLE or SINGLE to quote the results @version 9.2 @author Allan Bowe From 3791cb8a2c7535e4a2ea3b4dac72a8ecccfe4b61 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sun, 2 May 2021 19:10:37 +0300 Subject: [PATCH 47/70] feat: two new macros for checking a filter query, and then generating a filter query. One test to cover the generation part. One more macro to provide assertions on the number of rows in a table, compatible with the upcoming 'sasjs test' feature. --- base/mp_assertdsobs.sas | 67 ++++++++++++++ base/mp_filtercheck.sas | 143 +++++++++++++++++++++++++++++ base/mp_filtergenerate.sas | 90 ++++++++++++++++++ sasjs/sasjsconfig.json | 18 +++- tests/base/mp_filtercheck.test.sas | 128 ++++++++++++++++++++++++++ 5 files changed, 442 insertions(+), 4 deletions(-) create mode 100644 base/mp_assertdsobs.sas create mode 100644 base/mp_filtercheck.sas create mode 100644 base/mp_filtergenerate.sas create mode 100644 tests/base/mp_filtercheck.test.sas diff --git a/base/mp_assertdsobs.sas b/base/mp_assertdsobs.sas new file mode 100644 index 0000000..7726a5a --- /dev/null +++ b/base/mp_assertdsobs.sas @@ -0,0 +1,67 @@ +/** + @file + @brief Asserts the number of observations in a dataset + @details Useful in the context of writing sasjs tests. The results of the + test are _appended_ to the &outds. table. + + Example usage: + + %mp_assertdsobs(sashelp.class) %* tests if any observations are present; + +

SAS Macros

+ @li mf_nobs.sas + + + @param [in] inds input dataset to test for presence of observations + @param [in] desc= (Testing observations) The user provided test description + @param [in] test= (HASOBS) The test to apply. Valid values are: + @li HASOBS Test is a PASS if the input dataset has any observations + @li EMPTY Test is a PASS if input dataset is empty + @param [out] outds= (work.test_results) The output dataset to contain the + results. If it does not exist, it will be created, with the following format: + |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| + |---|---|---| + |User Provided description|PASS|Dataset &inds has XX obs| + + + @version 9.2 + @author Allan Bowe + +**/ + +%macro mp_assertdsobs(inds, + test=HASOBS, + desc=Testing observations, + outds=work.test_results +)/*/STORE SOURCE*/; + + %local nobs; + %let nobs=%mf_nobs(&inds); + %let test=%upcase(&test); + + data; + length test_description $256 test_result $4 test_comments $256; + test_description=symget('desc'); + test_result='FAIL'; + test_comments="&sysmacroname: Dataset &inds has &nobs observations"; + %if &test=HASOBS %then %do; + if &nobs>0 then test_result='PASS'; + %end; + %else %if &test=EMPTY %then %do; + if &nobs=0 then test_result='PASS'; + %end; + %else %do; + test_comments="&sysmacroname: Unsatisfied test condition - &test"; + %end; + run; + + %local ds; + %let ds=&syslast; + + proc append base=&outds data=&ds; + run; + + proc sql; + drop table &ds; + +%mend; \ No newline at end of file diff --git a/base/mp_filtercheck.sas b/base/mp_filtercheck.sas new file mode 100644 index 0000000..49ab1dc --- /dev/null +++ b/base/mp_filtercheck.sas @@ -0,0 +1,143 @@ +/** + @file + @brief Checks an input filter table for validity + @details Performs checks on the input table to ensure it arrives in the + correct format. This is necessary to prevent code injection. Will update + SYSCC to 1008 if bad records are found. + + Used for dynamic filtering in [Data Controller for SAS®](https://datacontroller.io). + + Usage: + + %mp_filtercheck(work.filter,targetds=sashelp.class,outds=work.badrecords) + + The input table should have the following format: + + |GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767| + |---|---|---|---|---|---| + |AND|AND|1|AGE|=|12| + |AND|AND|1|SEX|<=|'M'| + |AND|OR|2|Name|NOT IN|('Jane','Alfred')| + |AND|OR|2|Weight|>=|7| + + Rules applied: + + @li GROUP_LOGIC - only AND/OR + @li SUBGROUP_LOGIC - only AND/OR + @li SUBGROUP_ID - only integers + @li VARIABLE_NM - must be in the target table + @li OPERATOR_NM - only =/>/=/BETWEEN/IN/NOT IN/NOT EQUAL/CONTAINS + @li RAW_VALUE - no unquoted values except integers, commas and spaces. + + @returns The &outds table containing any bad rows, plus a REASON_CD column. + + @param [in] inds The table to be checked, with the format above + @param [in] targetds= The target dataset against which to verify VARIABLE_NM + @param [out] outds= The output table, which is a copy of the &inds. table + plus a REASON_CD column, containing only bad records. If bad records found, + the SYSCC value will be set to 1008 (general data problem). Downstream + processes should check this table (and return code) before continuing. + +

SAS Macros

+ @li mp_abort.sas + @li mf_getvarlist.sas + @li mf_nobs.sas + +

Related Macros

+ @li mp_filtergenerate.sas + + @version 9.3 + @author Allan Bowe + + @todo Support date / hex / name literals and exponents in RAW_VALUE field +**/ + +%macro mp_filtercheck(inds,targetds=,outds=work.badrecords); + +%mp_abort(iftrue= (&syscc ne 0) + ,mac=&sysmacroname + ,msg=%str(syscc=&syscc - on macro entry) +) + +/** + * Sanitise the values based on valid value lists, then strip out + * quotes, commas, periods and spaces. + * Only numeric values should remain + */ + +data &outds; + set &inds; + length reason_cd $32; + + /* closed list checks */ + if GROUP_LOGIC not in ('AND','OR') then do; + REASON_CD='GROUP_LOGIC should be either AND or OR'; + putlog REASON_CD= GROUP_LOGIC=; + output; + end; + if SUBGROUP_LOGIC not in ('AND','OR') then do; + REASON_CD='SUBGROUP_LOGIC should be either AND or OR'; + putlog REASON_CD= SUBGROUP_LOGIC=; + output; + end; + if mod(SUBGROUP_ID,1) ne 0 then do; + REASON_CD='SUBGROUP_ID should be integer'; + putlog REASON_CD= SUBGROUP_ID=; + output; + end; + if upcase(VARIABLE_NM) not in + (%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE))) + then do; + REASON_CD="VARIABLE_NM not in &targetds"; + putlog REASON_CD= VARIABLE_NM=; + output; + end; + if OPERATOR_NM not in + ('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NOT EQUAL','CONTAINS') + then do; + REASON_CD='Invalid OPERATOR_NM'; + putlog REASON_CD= OPERATOR_NM=; + output; + end; + + /* special logic */ + if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ',''); + else if OPERATOR_NM in ('IN','NOT IN') then do; + if substr(raw_value,1,1) ne '(' + or substr(cats(reverse(raw_value)),1,1) ne ')' + then do; + REASON_CD='Missing brackets in RAW_VALUE'; + putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ; + output; + end; + else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0)); + end; + else raw_value1=raw_value; + + /* remove nested literals eg '' */ + raw_value1=tranwrd(raw_value1,"''",''); + + /* now match string literals (always single quotes) */ + raw_value2=raw_value1; + regex = prxparse("s/(\').*?(\')//"); + call prxchange(regex,-1,raw_value2); + + /* remove commas */ + raw_value3=compress(raw_value2,','); + + + + + /* output records that contain values other than digits and spaces */ + if notdigit(compress(raw_value3,' '))>0 then do; + putlog raw_value3= $hex32.; + REASON_CD='Invalid RAW_VALUE'; + putlog REASON_CD= raw_value= raw_value1= raw_value2= raw_value3=; + output; + end; + +run; + +%if %mf_nobs(&outds)>0 %then %let syscc=1008; + +%mend; diff --git a/base/mp_filtergenerate.sas b/base/mp_filtergenerate.sas new file mode 100644 index 0000000..ac7c202 --- /dev/null +++ b/base/mp_filtergenerate.sas @@ -0,0 +1,90 @@ +/** + @file + @brief Generates a filter clause from an input table, to a fileref + @details Uses the input table to generate an output filter clause. + This feature is used to create dynamic dropdowns in [Data Controller for SAS®]( + https://datacontroller.io). The input table should be in the format below: + + |GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767| + |---|---|---|---|---|---| + |AND|AND|1|AGE|=|12| + |AND|AND|1|SEX|<=|'M'| + |AND|OR|2|Name|NOT IN|('Jane','Alfred')| + |AND|OR|2|Weight|>=|7| + + Note - if the above table is received from an external client, the values + should first be validated using the mp_filtercheck.sas macro to avoid risk + of SQL injection. + + To generate the filter, run the following code: + + data work.filtertable; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$32767.; + datalines4; + AND,AND,1,AGE,=,12 + AND,AND,1,SEX,<=,"'M'" + AND,OR,2,Name,NOT IN,"('Jane','Alfred')" + AND,OR,2,Weight,>=,7 + ;;;; + run; + + %mp_filtergenerate(work.filtertable,outref=myfilter) + + data _null_; + infile myfilter; + input; + put _infile_; + run; + + Will write the following query to the log: + + > ( + > AGE = 12 + > AND + > SEX <= 'M' + > ) AND ( + > Name NOT IN ('Jane','Alfred') + > OR + > Weight >= 7 + > ) + + @param [in] inds The input table with query values + @param [out] outref= The output fileref to contain the filter clause. Will + be created (or replaced). + +

Related Macros

+ @li mp_filtercheck.sas + +

SAS Macros

+ @li mp_abort.sas + + @version 9.3 + @author Allan Bowe + +**/ + +%macro mp_filtergenerate(inds,outref=filter); + +%mp_abort(iftrue= (&syscc ne 0) + ,mac=&sysmacroname + ,msg=%str(syscc=&syscc - on macro entry) +) + +filename &outref temp; + +data _null_; + file &outref lrecl=32800; + set &inds end=last; + by SUBGROUP_ID; + if _n_=1 then put '('; + else if first.SUBGROUP_ID then put +1 GROUP_LOGIC '('; + else put +2 SUBGROUP_LOGIC; + + put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE; + + if last.SUBGROUP_ID then put ')'@; +run; + +%mend; diff --git a/sasjs/sasjsconfig.json b/sasjs/sasjsconfig.json index 192034e..2e10dd2 100644 --- a/sasjs/sasjsconfig.json +++ b/sasjs/sasjsconfig.json @@ -1,6 +1,12 @@ { "$schema": "https://cli.sasjs.io/sasjsconfig-schema.json", - "macroFolders": ["base", "meta", "metax", "viya", "lua"], + "macroFolders": [ + "base", + "meta", + "metax", + "viya", + "lua" + ], "docConfig": { "displayMacroCore": false, "enableLineage": false, @@ -21,14 +27,18 @@ "serverType": "SASVIYA", "appLoc": "/Public/temp/macrocore", "serviceConfig": { - "serviceFolders": ["tests/viya"], + "serviceFolders": [ + "tests/base", + "tests/viya" + ], "macroVars": { "mcTestAppLoc": "/Public/temp/macrocore" } }, "deployConfig": { "deployServicePack": true - } + }, + "contextName": "SAS Job Execution compute context" } ] -} +} \ No newline at end of file diff --git a/tests/base/mp_filtercheck.test.sas b/tests/base/mp_filtercheck.test.sas new file mode 100644 index 0000000..6c957ff --- /dev/null +++ b/tests/base/mp_filtercheck.test.sas @@ -0,0 +1,128 @@ +/** + @file + @brief Testing mp_filtercheck macro + +

SAS Macros

+ @li mp_filtercheck.sas + @li mp_assertdsobs.sas + +**/ + + +/* valid filter */ +data work.inds; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$32767.; +datalines4; +AND,AND,1,AGE,=,12 +AND,AND,1,SEX,<=,"'M'" +AND,OR,2,Name,NOT IN,"('Jane','Alfred')" +AND,OR,2,Weight,>=,7 +;;;; +run; + +%mp_filtercheck(work.inds, + targetds=sashelp.class, + outds=work.badrecords +) +%let syscc=0; +%mp_assertdsobs(work.badrecords, + desc=Valid filter query, + test=EMPTY, + outds=work.test_results +) + +/* invalid column */ +data work.inds; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$32767.; +datalines4; +AND,AND,1,invalid,=,12 +AND,AND,1,SEX,<=,"'M'" +AND,OR,2,Name,NOT IN,"('Jane','Alfred')" +AND,OR,2,Weight,>=,7 +;;;; +run; + +%mp_filtercheck(work.inds, + targetds=sashelp.class, + outds=work.badrecords +) +%let syscc=0; +%mp_assertdsobs(work.badrecords, + desc=Invalid column name, + test=HASOBS, + outds=work.test_results +) + +/* invalid raw value */ +data work.inds; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$32767.; +datalines4; +AND,OR,2,Name,NOT IN,"(''''Jane','Alfred')" +;;;; +run; + +%mp_filtercheck(work.inds, + targetds=sashelp.class, + outds=work.badrecords +) +%let syscc=0; +%mp_assertdsobs(work.badrecords, + desc=Invalid raw value, + test=HASOBS, + outds=work.test_results +) + +/* Code injection - column name */ +data work.inds; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$32767.; +datalines4; +AND,AND,1,%abort,=,12 +AND,OR,2,Weight,>=,7 +;;;; +run; + +%mp_filtercheck(work.inds, + targetds=sashelp.class, + outds=work.badrecords +) +%let syscc=0; +%mp_assertdsobs(work.badrecords, + desc=Code injection - column name, + test=HASOBS, + outds=work.test_results +) + +/* Code injection - raw values*/ +data work.inds; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$32767.; +datalines4; +AND,AND,1,age,=,;;%abort +;;;; +run; + +%mp_filtercheck(work.inds, + targetds=sashelp.class, + outds=work.badrecords +) +%let syscc=0; +%mp_assertdsobs(work.badrecords, + desc=Code injection - raw value abort, + test=HASOBS, + outds=work.test_results +) + + + +%webout(OPEN) +%webout(OBJ, TEST_RESULTS) +%webout(CLOSE) \ No newline at end of file From 7d7608f06ccad95d9efea83f3536419395366342 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sun, 2 May 2021 19:12:08 +0300 Subject: [PATCH 48/70] chore: updating all.sas --- all.sas | 299 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) diff --git a/all.sas b/all.sas index a7c2b12..298df2e 100644 --- a/all.sas +++ b/all.sas @@ -1782,6 +1782,72 @@ Usage: %mend; /** @endcond *//** + @file + @brief Asserts the number of observations in a dataset + @details Useful in the context of writing sasjs tests. The results of the + test are _appended_ to the &outds. table. + + Example usage: + + %mp_assertdsobs(sashelp.class) %* tests if any observations are present; + +

SAS Macros

+ @li mf_nobs.sas + + + @param [in] inds input dataset to test for presence of observations + @param [in] desc= (Testing observations) The user provided test description + @param [in] test= (HASOBS) The test to apply. Valid values are: + @li HASOBS Test is a PASS if the input dataset has any observations + @li EMPTY Test is a PASS if input dataset is empty + @param [out] outds= (work.test_results) The output dataset to contain the + results. If it does not exist, it will be created, with the following format: + |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| + |---|---|---| + |User Provided description|PASS|Dataset &inds has XX obs| + + + @version 9.2 + @author Allan Bowe + +**/ + +%macro mp_assertdsobs(inds, + test=HASOBS, + desc=Testing observations, + outds=work.test_results +)/*/STORE SOURCE*/; + + %local nobs; + %let nobs=%mf_nobs(&inds); + %let test=%upcase(&test); + + data; + length test_description $256 test_result $4 test_comments $256; + test_description=symget('desc'); + test_result='FAIL'; + test_comments="&sysmacroname: Dataset &inds has &nobs observations"; + %if &test=HASOBS %then %do; + if &nobs>0 then test_result='PASS'; + %end; + %else %if &test=EMPTY %then %do; + if &nobs=0 then test_result='PASS'; + %end; + %else %do; + test_comments="&sysmacroname: Unsatisfied test condition - &test"; + %end; + run; + + %local ds; + %let ds=&syslast; + + proc append base=&outds data=&ds; + run; + + proc sql; + drop table &ds; + +%mend;/** @file @brief Copy any file using binary input / output streams @details Reads in a file byte by byte and writes it back out. Is an @@ -2815,6 +2881,239 @@ run; %mend;/** + @file + @brief Checks an input filter table for validity + @details Performs checks on the input table to ensure it arrives in the + correct format. This is necessary to prevent code injection. Will update + SYSCC to 1008 if bad records are found. + + Used for dynamic filtering in [Data Controller for SAS®](https://datacontroller.io). + + Usage: + + %mp_filtercheck(work.filter,targetds=sashelp.class,outds=work.badrecords) + + The input table should have the following format: + + |GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767| + |---|---|---|---|---|---| + |AND|AND|1|AGE|=|12| + |AND|AND|1|SEX|<=|'M'| + |AND|OR|2|Name|NOT IN|('Jane','Alfred')| + |AND|OR|2|Weight|>=|7| + + Rules applied: + + @li GROUP_LOGIC - only AND/OR + @li SUBGROUP_LOGIC - only AND/OR + @li SUBGROUP_ID - only integers + @li VARIABLE_NM - must be in the target table + @li OPERATOR_NM - only =/>/=/BETWEEN/IN/NOT IN/NOT EQUAL/CONTAINS + @li RAW_VALUE - no unquoted values except integers, commas and spaces. + + @returns The &outds table containing any bad rows, plus a REASON_CD column. + + @param [in] inds The table to be checked, with the format above + @param [in] targetds= The target dataset against which to verify VARIABLE_NM + @param [out] outds= The output table, which is a copy of the &inds. table + plus a REASON_CD column, containing only bad records. If bad records found, + the SYSCC value will be set to 1008 (general data problem). Downstream + processes should check this table (and return code) before continuing. + +

SAS Macros

+ @li mp_abort.sas + @li mf_getvarlist.sas + @li mf_nobs.sas + +

Related Macros

+ @li mp_filtergenerate.sas + + @version 9.3 + @author Allan Bowe + + @todo Support date / hex / name literals and exponents in RAW_VALUE field +**/ + +%macro mp_filtercheck(inds,targetds=,outds=work.badrecords); + +%mp_abort(iftrue= (&syscc ne 0) + ,mac=&sysmacroname + ,msg=%str(syscc=&syscc - on macro entry) +) + +/** + * Sanitise the values based on valid value lists, then strip out + * quotes, commas, periods and spaces. + * Only numeric values should remain + */ + +data &outds; + set &inds; + length reason_cd $32; + + /* closed list checks */ + if GROUP_LOGIC not in ('AND','OR') then do; + REASON_CD='GROUP_LOGIC should be either AND or OR'; + putlog REASON_CD= GROUP_LOGIC=; + output; + end; + if SUBGROUP_LOGIC not in ('AND','OR') then do; + REASON_CD='SUBGROUP_LOGIC should be either AND or OR'; + putlog REASON_CD= SUBGROUP_LOGIC=; + output; + end; + if mod(SUBGROUP_ID,1) ne 0 then do; + REASON_CD='SUBGROUP_ID should be integer'; + putlog REASON_CD= SUBGROUP_ID=; + output; + end; + if upcase(VARIABLE_NM) not in + (%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE))) + then do; + REASON_CD="VARIABLE_NM not in &targetds"; + putlog REASON_CD= VARIABLE_NM=; + output; + end; + if OPERATOR_NM not in + ('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NOT EQUAL','CONTAINS') + then do; + REASON_CD='Invalid OPERATOR_NM'; + putlog REASON_CD= OPERATOR_NM=; + output; + end; + + /* special logic */ + if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ',''); + else if OPERATOR_NM in ('IN','NOT IN') then do; + if substr(raw_value,1,1) ne '(' + or substr(cats(reverse(raw_value)),1,1) ne ')' + then do; + REASON_CD='Missing brackets in RAW_VALUE'; + putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ; + output; + end; + else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0)); + end; + else raw_value1=raw_value; + + /* remove nested literals eg '' */ + raw_value1=tranwrd(raw_value1,"''",''); + + /* now match string literals (always single quotes) */ + raw_value2=raw_value1; + regex = prxparse("s/(\').*?(\')//"); + call prxchange(regex,-1,raw_value2); + + /* remove commas */ + raw_value3=compress(raw_value2,','); + + + + + /* output records that contain values other than digits and spaces */ + if notdigit(compress(raw_value3,' '))>0 then do; + putlog raw_value3= $hex32.; + REASON_CD='Invalid RAW_VALUE'; + putlog REASON_CD= raw_value= raw_value1= raw_value2= raw_value3=; + output; + end; + +run; + +%if %mf_nobs(&outds)>0 %then %let syscc=1008; + +%mend; +/** + @file + @brief Generates a filter clause from an input table, to a fileref + @details Uses the input table to generate an output filter clause. + This feature is used to create dynamic dropdowns in [Data Controller for SAS®]( + https://datacontroller.io). The input table should be in the format below: + + |GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767| + |---|---|---|---|---|---| + |AND|AND|1|AGE|=|12| + |AND|AND|1|SEX|<=|'M'| + |AND|OR|2|Name|NOT IN|('Jane','Alfred')| + |AND|OR|2|Weight|>=|7| + + Note - if the above table is received from an external client, the values + should first be validated using the mp_filtercheck.sas macro to avoid risk + of SQL injection. + + To generate the filter, run the following code: + + data work.filtertable; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$32767.; + datalines4; + AND,AND,1,AGE,=,12 + AND,AND,1,SEX,<=,"'M'" + AND,OR,2,Name,NOT IN,"('Jane','Alfred')" + AND,OR,2,Weight,>=,7 + ;;;; + run; + + %mp_filtergenerate(work.filtertable,outref=myfilter) + + data _null_; + infile myfilter; + input; + put _infile_; + run; + + Will write the following query to the log: + + > ( + > AGE = 12 + > AND + > SEX <= 'M' + > ) AND ( + > Name NOT IN ('Jane','Alfred') + > OR + > Weight >= 7 + > ) + + @param [in] inds The input table with query values + @param [out] outref= The output fileref to contain the filter clause. Will + be created (or replaced). + +

Related Macros

+ @li mp_filtercheck.sas + +

SAS Macros

+ @li mp_abort.sas + + @version 9.3 + @author Allan Bowe + +**/ + +%macro mp_filtergenerate(inds,outref=filter); + +%mp_abort(iftrue= (&syscc ne 0) + ,mac=&sysmacroname + ,msg=%str(syscc=&syscc - on macro entry) +) + +filename &outref temp; + +data _null_; + file &outref lrecl=32800; + set &inds end=last; + by SUBGROUP_ID; + if _n_=1 then put '('; + else if first.SUBGROUP_ID then put +1 GROUP_LOGIC '('; + else put +2 SUBGROUP_LOGIC; + + put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE; + + if last.SUBGROUP_ID then put ')'@; +run; + +%mend; +/** @file mp_getconstraints.sas @brief Get constraint details at column level @details Useful for capturing constraints before they are dropped / reapplied From 369c4412f3602e73e7cc8f311088fc317e6ec76c Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sun, 2 May 2021 22:11:44 +0300 Subject: [PATCH 49/70] fix: adding tests for mp_filtergenerate, also updating the corresponding macros following test results --- all.sas | 86 +++++++++++++----- base/mp_assertdsobs.sas | 24 ++++- base/mp_filtercheck.sas | 32 +++++-- base/mp_filtergenerate.sas | 30 ++++-- tests/base/mp_filtercheck.test.sas | 20 ++-- tests/base/mp_filtergenerate.test.sas | 126 ++++++++++++++++++++++++++ 6 files changed, 268 insertions(+), 50 deletions(-) create mode 100644 tests/base/mp_filtergenerate.test.sas diff --git a/all.sas b/all.sas index 298df2e..20cce9a 100644 --- a/all.sas +++ b/all.sas @@ -1793,13 +1793,15 @@ Usage:

SAS Macros

@li mf_nobs.sas + @li mp_abort.sas @param [in] inds input dataset to test for presence of observations @param [in] desc= (Testing observations) The user provided test description @param [in] test= (HASOBS) The test to apply. Valid values are: - @li HASOBS Test is a PASS if the input dataset has any observations - @li EMPTY Test is a PASS if input dataset is empty + @li HASOBS - Test is a PASS if the input dataset has any observations + @li EMPTY - Test is a PASS if input dataset is empty + @li EQUALS [integer] - Test passes if obs count matches the provided integer @param [out] outds= (work.test_results) The output dataset to contain the results. If it does not exist, it will be created, with the following format: |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| @@ -1822,6 +1824,21 @@ Usage: %let nobs=%mf_nobs(&inds); %let test=%upcase(&test); + %if %substr(&test.xxxxx,1,6)=EQUALS %then %do; + %let val=%scan(&test,2,%str( )); + %mp_abort(iftrue= (%DATATYP(&val)=CHAR) + ,mac=&sysmacroname + ,msg=%str(Invalid test - &test, expected EQUALS [integer]) + ) + %let test=EQUALS; + %end; + %else %if &test ne HASOBS and &test ne EMPTY %then %do; + %mp_abort( + mac=&sysmacroname, + msg=%str(Invalid test - &test) + ) + %end; + data; length test_description $256 test_result $4 test_comments $256; test_description=symget('desc'); @@ -1833,6 +1850,9 @@ Usage: %else %if &test=EMPTY %then %do; if &nobs=0 then test_result='PASS'; %end; + %else %if &test=EQUALS %then %do; + if &nobs=&val then test_result='PASS'; + %end; %else %do; test_comments="&sysmacroname: Unsatisfied test condition - &test"; %end; @@ -2885,7 +2905,8 @@ run; @brief Checks an input filter table for validity @details Performs checks on the input table to ensure it arrives in the correct format. This is necessary to prevent code injection. Will update - SYSCC to 1008 if bad records are found. + SYSCC to 1008 if bad records are found, and call mp_abort.sas for a + graceful service exit (configurable). Used for dynamic filtering in [Data Controller for SAS®](https://datacontroller.io). @@ -2915,6 +2936,7 @@ run; @param [in] inds The table to be checked, with the format above @param [in] targetds= The target dataset against which to verify VARIABLE_NM + @param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions @param [out] outds= The output table, which is a copy of the &inds. table plus a REASON_CD column, containing only bad records. If bad records found, the SYSCC value will be set to 1008 (general data problem). Downstream @@ -2934,7 +2956,7 @@ run; @todo Support date / hex / name literals and exponents in RAW_VALUE field **/ -%macro mp_filtercheck(inds,targetds=,outds=work.badrecords); +%macro mp_filtercheck(inds,targetds=,outds=work.badrecords,abort=YES); %mp_abort(iftrue= (&syscc ne 0) ,mac=&sysmacroname @@ -2975,7 +2997,7 @@ data &outds; output; end; if OPERATOR_NM not in - ('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NOT EQUAL','CONTAINS') + ('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS') then do; REASON_CD='Invalid OPERATOR_NM'; putlog REASON_CD= OPERATOR_NM=; @@ -3004,11 +3026,8 @@ data &outds; regex = prxparse("s/(\').*?(\')//"); call prxchange(regex,-1,raw_value2); - /* remove commas */ - raw_value3=compress(raw_value2,','); - - - + /* remove commas and periods*/ + raw_value3=compress(raw_value2,',.'); /* output records that contain values other than digits and spaces */ if notdigit(compress(raw_value3,' '))>0 then do; @@ -3020,7 +3039,22 @@ data &outds; run; -%if %mf_nobs(&outds)>0 %then %let syscc=1008; +%if %mf_nobs(&outds)>0 %then %do; + %if &abort=YES %then %do; + data _null_; + set &outds; + call symputx('REASON_CD',reason_cd,'l'); + stop; + run; + %mp_abort( + mac=&sysmacroname, + msg=%str(Filter issues in &inds, first was &reason_cd, details in &outds) + ) + %end; + %let syscc=1008; +%end; + + %mend; /** @@ -3084,6 +3118,7 @@ run;

SAS Macros

@li mp_abort.sas + @li mf_nobs.sas @version 9.3 @author Allan Bowe @@ -3099,18 +3134,27 @@ run; filename &outref temp; -data _null_; - file &outref lrecl=32800; - set &inds end=last; - by SUBGROUP_ID; - if _n_=1 then put '('; - else if first.SUBGROUP_ID then put +1 GROUP_LOGIC '('; - else put +2 SUBGROUP_LOGIC; +%if %mf_nobs(&inds)=0 %then %do; + /* ensure we have a default filter */ + data _null_; + file &outref; + put '1=1'; + run; +%end; +%else %do; + data _null_; + file &outref lrecl=32800; + set &inds end=last; + by SUBGROUP_ID; + if _n_=1 then put '('; + else if first.SUBGROUP_ID then put +1 GROUP_LOGIC '('; + else put +2 SUBGROUP_LOGIC; - put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE; + put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE; - if last.SUBGROUP_ID then put ')'@; -run; + if last.SUBGROUP_ID then put ')'@; + run; +%end; %mend; /** diff --git a/base/mp_assertdsobs.sas b/base/mp_assertdsobs.sas index 7726a5a..1769ffe 100644 --- a/base/mp_assertdsobs.sas +++ b/base/mp_assertdsobs.sas @@ -10,13 +10,15 @@

SAS Macros

@li mf_nobs.sas + @li mp_abort.sas @param [in] inds input dataset to test for presence of observations @param [in] desc= (Testing observations) The user provided test description @param [in] test= (HASOBS) The test to apply. Valid values are: - @li HASOBS Test is a PASS if the input dataset has any observations - @li EMPTY Test is a PASS if input dataset is empty + @li HASOBS - Test is a PASS if the input dataset has any observations + @li EMPTY - Test is a PASS if input dataset is empty + @li EQUALS [integer] - Test passes if obs count matches the provided integer @param [out] outds= (work.test_results) The output dataset to contain the results. If it does not exist, it will be created, with the following format: |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| @@ -39,6 +41,21 @@ %let nobs=%mf_nobs(&inds); %let test=%upcase(&test); + %if %substr(&test.xxxxx,1,6)=EQUALS %then %do; + %let val=%scan(&test,2,%str( )); + %mp_abort(iftrue= (%DATATYP(&val)=CHAR) + ,mac=&sysmacroname + ,msg=%str(Invalid test - &test, expected EQUALS [integer]) + ) + %let test=EQUALS; + %end; + %else %if &test ne HASOBS and &test ne EMPTY %then %do; + %mp_abort( + mac=&sysmacroname, + msg=%str(Invalid test - &test) + ) + %end; + data; length test_description $256 test_result $4 test_comments $256; test_description=symget('desc'); @@ -50,6 +67,9 @@ %else %if &test=EMPTY %then %do; if &nobs=0 then test_result='PASS'; %end; + %else %if &test=EQUALS %then %do; + if &nobs=&val then test_result='PASS'; + %end; %else %do; test_comments="&sysmacroname: Unsatisfied test condition - &test"; %end; diff --git a/base/mp_filtercheck.sas b/base/mp_filtercheck.sas index 49ab1dc..79343ac 100644 --- a/base/mp_filtercheck.sas +++ b/base/mp_filtercheck.sas @@ -3,7 +3,8 @@ @brief Checks an input filter table for validity @details Performs checks on the input table to ensure it arrives in the correct format. This is necessary to prevent code injection. Will update - SYSCC to 1008 if bad records are found. + SYSCC to 1008 if bad records are found, and call mp_abort.sas for a + graceful service exit (configurable). Used for dynamic filtering in [Data Controller for SAS®](https://datacontroller.io). @@ -33,6 +34,7 @@ @param [in] inds The table to be checked, with the format above @param [in] targetds= The target dataset against which to verify VARIABLE_NM + @param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions @param [out] outds= The output table, which is a copy of the &inds. table plus a REASON_CD column, containing only bad records. If bad records found, the SYSCC value will be set to 1008 (general data problem). Downstream @@ -52,7 +54,7 @@ @todo Support date / hex / name literals and exponents in RAW_VALUE field **/ -%macro mp_filtercheck(inds,targetds=,outds=work.badrecords); +%macro mp_filtercheck(inds,targetds=,outds=work.badrecords,abort=YES); %mp_abort(iftrue= (&syscc ne 0) ,mac=&sysmacroname @@ -93,7 +95,7 @@ data &outds; output; end; if OPERATOR_NM not in - ('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NOT EQUAL','CONTAINS') + ('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS') then do; REASON_CD='Invalid OPERATOR_NM'; putlog REASON_CD= OPERATOR_NM=; @@ -122,11 +124,8 @@ data &outds; regex = prxparse("s/(\').*?(\')//"); call prxchange(regex,-1,raw_value2); - /* remove commas */ - raw_value3=compress(raw_value2,','); - - - + /* remove commas and periods*/ + raw_value3=compress(raw_value2,',.'); /* output records that contain values other than digits and spaces */ if notdigit(compress(raw_value3,' '))>0 then do; @@ -138,6 +137,21 @@ data &outds; run; -%if %mf_nobs(&outds)>0 %then %let syscc=1008; +%if %mf_nobs(&outds)>0 %then %do; + %if &abort=YES %then %do; + data _null_; + set &outds; + call symputx('REASON_CD',reason_cd,'l'); + stop; + run; + %mp_abort( + mac=&sysmacroname, + msg=%str(Filter issues in &inds, first was &reason_cd, details in &outds) + ) + %end; + %let syscc=1008; +%end; + + %mend; diff --git a/base/mp_filtergenerate.sas b/base/mp_filtergenerate.sas index ac7c202..d6ef9f7 100644 --- a/base/mp_filtergenerate.sas +++ b/base/mp_filtergenerate.sas @@ -59,6 +59,7 @@

SAS Macros

@li mp_abort.sas + @li mf_nobs.sas @version 9.3 @author Allan Bowe @@ -74,17 +75,26 @@ filename &outref temp; -data _null_; - file &outref lrecl=32800; - set &inds end=last; - by SUBGROUP_ID; - if _n_=1 then put '('; - else if first.SUBGROUP_ID then put +1 GROUP_LOGIC '('; - else put +2 SUBGROUP_LOGIC; +%if %mf_nobs(&inds)=0 %then %do; + /* ensure we have a default filter */ + data _null_; + file &outref; + put '1=1'; + run; +%end; +%else %do; + data _null_; + file &outref lrecl=32800; + set &inds end=last; + by SUBGROUP_ID; + if _n_=1 then put '('; + else if first.SUBGROUP_ID then put +1 GROUP_LOGIC '('; + else put +2 SUBGROUP_LOGIC; - put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE; + put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE; - if last.SUBGROUP_ID then put ')'@; -run; + if last.SUBGROUP_ID then put ')'@; + run; +%end; %mend; diff --git a/tests/base/mp_filtercheck.test.sas b/tests/base/mp_filtercheck.test.sas index 6c957ff..5cce1b4 100644 --- a/tests/base/mp_filtercheck.test.sas +++ b/tests/base/mp_filtercheck.test.sas @@ -18,13 +18,15 @@ datalines4; AND,AND,1,AGE,=,12 AND,AND,1,SEX,<=,"'M'" AND,OR,2,Name,NOT IN,"('Jane','Alfred')" -AND,OR,2,Weight,>=,7 +AND,OR,2,Weight,>=,77.7 +AND,OR,2,Weight,NE,77.7 ;;;; run; %mp_filtercheck(work.inds, targetds=sashelp.class, - outds=work.badrecords + outds=work.badrecords, + abort=NO ) %let syscc=0; %mp_assertdsobs(work.badrecords, @@ -45,10 +47,10 @@ AND,OR,2,Name,NOT IN,"('Jane','Alfred')" AND,OR,2,Weight,>=,7 ;;;; run; - %mp_filtercheck(work.inds, targetds=sashelp.class, - outds=work.badrecords + outds=work.badrecords, + abort=NO ) %let syscc=0; %mp_assertdsobs(work.badrecords, @@ -69,7 +71,8 @@ run; %mp_filtercheck(work.inds, targetds=sashelp.class, - outds=work.badrecords + outds=work.badrecords, + abort=NO ) %let syscc=0; %mp_assertdsobs(work.badrecords, @@ -91,7 +94,8 @@ run; %mp_filtercheck(work.inds, targetds=sashelp.class, - outds=work.badrecords + outds=work.badrecords, + abort=NO ) %let syscc=0; %mp_assertdsobs(work.badrecords, @@ -109,10 +113,10 @@ datalines4; AND,AND,1,age,=,;;%abort ;;;; run; - %mp_filtercheck(work.inds, targetds=sashelp.class, - outds=work.badrecords + outds=work.badrecords, + abort=NO ) %let syscc=0; %mp_assertdsobs(work.badrecords, diff --git a/tests/base/mp_filtergenerate.test.sas b/tests/base/mp_filtergenerate.test.sas new file mode 100644 index 0000000..be9e1c3 --- /dev/null +++ b/tests/base/mp_filtergenerate.test.sas @@ -0,0 +1,126 @@ +/** + @file + @brief Testing mp_filtergenerate macro + +

SAS Macros

+ @li mp_filtergenerate.sas + @li mp_filtercheck.sas + @li mp_assertdsobs.sas + +**/ + +options source2; + +/* valid filter */ +data work.inds; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$32767.; +datalines4; +AND,AND,1,AGE,>,5 +AND,AND,1,SEX,NE,"'M'" +AND,OR,2,Name,NOT IN,"('Jane','Janet')" +AND,OR,2,Weight,>=,84.6 +;;;; +run; +%mp_filtercheck(work.inds,targetds=sashelp.class) +%mp_filtergenerate(work.inds,outref=myfilter) +data work.test; + set sashelp.class; + where %inc myfilter;; +run; +%mp_assertdsobs(work.test, + desc=Valid filter, + test=EQUALS 8, + outds=work.test_results +) + +/* empty filter (return all records) */ +data work.inds; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$32767.; +datalines4; +;;;; +run; +%mp_filtercheck(work.inds,targetds=sashelp.class) +%mp_filtergenerate(work.inds,outref=myfilter) +data work.test; + set sashelp.class; + where %inc myfilter;; +run; +%mp_assertdsobs(work.test, + desc=Empty filter (return all records) , + test=EQUALS 19, + outds=work.test_results +) + +/* single line filter */ +data work.inds; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$32767.; +datalines4; +AND,OR,2,Name,IN,"('Jane','Janet')" +;;;; +run; +%mp_filtercheck(work.inds,targetds=sashelp.class) +%mp_filtergenerate(work.inds,outref=myfilter) +data work.test; + set sashelp.class; + where %inc myfilter;; +run; +%mp_assertdsobs(work.test, + desc=Single line filter , + test=EQUALS 2, + outds=work.test_results +) + +/* single line 2 group filter */ +data work.inds; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$32767.; +datalines4; +OR,OR,2,Name,IN,"('Jane','Janet')" +OR,OR,3,Name,IN,"('James')" +;;;; +run; +%mp_filtercheck(work.inds,targetds=sashelp.class) +%mp_filtergenerate(work.inds,outref=myfilter) +data work.test; + set sashelp.class; + where %inc myfilter;; +run; +%mp_assertdsobs(work.test, + desc=Single line 2 group filter , + test=EQUALS 3, + outds=work.test_results +) + +/* filter with nothing returned */ +data work.inds; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$32767.; +datalines4; +AND,OR,2,Name,IN,"('Jane','Janet')" +AND,OR,3,Name,IN,"('James')" +;;;; +run; +%mp_filtercheck(work.inds,targetds=sashelp.class) +%mp_filtergenerate(work.inds,outref=myfilter) +data work.test; + set sashelp.class; + where %inc myfilter;; +run; +%mp_assertdsobs(work.test, + desc=Filter with nothing returned, + test=EQUALS 0, + outds=work.test_results +) + + +%webout(OPEN) +%webout(OBJ, TEST_RESULTS) +%webout(CLOSE) \ No newline at end of file From 98118adb9a31ec19ee8c0428b40a56a2a2528254 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Mon, 3 May 2021 01:00:36 +0300 Subject: [PATCH 50/70] feat: new assertion macro for testing the values in a target column. Designed for use with 'sasjs test'. --- all.sas | 148 +++++++++++++++++++++++++++ base/mp_assertcolvals.sas | 147 ++++++++++++++++++++++++++ base/mp_assertdsobs.sas | 2 + tests/base/mp_assertcolvals.test.sas | 36 +++++++ 4 files changed, 333 insertions(+) create mode 100644 base/mp_assertcolvals.sas create mode 100644 tests/base/mp_assertcolvals.test.sas diff --git a/all.sas b/all.sas index 20cce9a..9e7265a 100644 --- a/all.sas +++ b/all.sas @@ -1782,6 +1782,152 @@ Usage: %mend; /** @endcond *//** + @file + @brief Asserts the values in a column + @details Useful in the context of writing sasjs tests. The results of the + test are _appended_ to the &outds. table. + + Example usage: + + data work.checkds; + do checkval='Jane','James','Jill'; + output; + end; + run; + %mp_assertcolvals(sashelp.class.name, + checkvals=work.checkds.checkval, + desc=At least one value has a match, + test=ANYVAL + ) + + data work.check; + do val='M','F'; + output; + end; + run; + %mp_assertcolvals(sashelp.class.sex, + checkvals=work.check.val, + desc=All values have a match, + test=ALLVALS + ) + +

SAS Macros

+ @li mf_existds.sas + @li mf_nobs.sas + @li mp_abort.sas + + + @param [in] indscol The input library.dataset.column to test for values + @param [in] checkvals= A library.dataset.column value containing a UNIQUE + list of values to be compared against the source (indscol). + @param [in] desc= (Testing observations) The user provided test description + @param [in] test= (ALLVALS) The test to apply. Valid values are: + @li ALLVALS - Test is a PASS if ALL values have a match in checkvals + @li ANYVAL - Test is a PASS if at least 1 value has a match in checkvals + @param [out] outds= (work.test_results) The output dataset to contain the + results. If it does not exist, it will be created, with the following format: + |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| + |---|---|---| + |User Provided description|PASS|Column &indscol contained ALL target vals| + + +

Related Macros

+ @li mp_assertdsobs.sas + + @version 9.2 + @author Allan Bowe + +**/ + +%macro mp_assertcolvals(indscol, + checkvals=0, + test=ALLVALS, + desc=mp_assertcolvals - no desc provided, + outds=work.test_results +)/*/STORE SOURCE*/; + + %mp_abort(iftrue= (&syscc ne 0) + ,mac=&sysmacroname + ,msg=%str(syscc=&syscc - on macro entry) + ) + + %local lib ds col clib cds ccol nobs; + %let lib=%scan(&indscol,1,%str(.)); + %let ds=%scan(&indscol,2,%str(.)); + %let col=%scan(&indscol,3,%str(.)); + %mp_abort(iftrue= (%mf_existds(&lib..&ds)=0) + ,mac=&sysmacroname + ,msg=%str(&lib..&ds not found!) + ) + + %mp_abort(iftrue= (&checkvals=0) + ,mac=&sysmacroname + ,msg=%str(Set CHECKVALS to a library.dataset.column containing check vals) + ) + %let clib=%scan(&checkvals,1,%str(.)); + %let cds=%scan(&checkvals,2,%str(.)); + %let ccol=%scan(&checkvals,3,%str(.)); + %mp_abort(iftrue= (%mf_existds(&clib..&cds)=0) + ,mac=&sysmacroname + ,msg=%str(&clib..&cds not found!) + ) + %let nobs=%mf_nobs(&clib..&cds); + %mp_abort(iftrue= (&nobs=0) + ,mac=&sysmacroname + ,msg=%str(&clib..&cds is empty!) + ) + + %let test=%upcase(&test); + + %if &test ne ALLVALS and &test ne ANYVAL %then %do; + %mp_abort( + mac=&sysmacroname, + msg=%str(Invalid test - &test) + ) + %end; + + %local result orig; + %let result=-1; + %let orig=-1; + proc sql noprint; + select count(*) into: result + from &lib..&ds + where &col not in ( + select &ccol from &clib..&cds + ); + select count(*) into: orig from &lib..&ds; + quit; + + %mp_abort(iftrue= (&syscc ne 0) + ,mac=&sysmacroname + ,msg=%str(syscc=&syscc after macro query) + ) + + data; + length test_description $256 test_result $4 test_comments $256; + test_description=symget('desc'); + test_result='FAIL'; + test_comments="&sysmacroname: &lib..&ds..&col has &result values " + !!"not in &clib..&cds..&ccol "; + %if &test=ANYVAL %then %do; + if &result < &orig then test_result='PASS'; + %end; + %else %if &test=ALLVALS %then %do; + if &result=0 then test_result='PASS'; + %end; + %else %do; + test_comments="&sysmacroname: Unsatisfied test condition - &test"; + %end; + run; + + %local ds; + %let ds=&syslast; + proc append base=&outds data=&ds; + run; + proc sql; + drop table &ds; + +%mend;/** @file @brief Asserts the number of observations in a dataset @details Useful in the context of writing sasjs tests. The results of the @@ -1808,6 +1954,8 @@ Usage: |---|---|---| |User Provided description|PASS|Dataset &inds has XX obs| +

Related Macros

+ @li mp_assertcolvals.sas @version 9.2 @author Allan Bowe diff --git a/base/mp_assertcolvals.sas b/base/mp_assertcolvals.sas new file mode 100644 index 0000000..298ee76 --- /dev/null +++ b/base/mp_assertcolvals.sas @@ -0,0 +1,147 @@ +/** + @file + @brief Asserts the values in a column + @details Useful in the context of writing sasjs tests. The results of the + test are _appended_ to the &outds. table. + + Example usage: + + data work.checkds; + do checkval='Jane','James','Jill'; + output; + end; + run; + %mp_assertcolvals(sashelp.class.name, + checkvals=work.checkds.checkval, + desc=At least one value has a match, + test=ANYVAL + ) + + data work.check; + do val='M','F'; + output; + end; + run; + %mp_assertcolvals(sashelp.class.sex, + checkvals=work.check.val, + desc=All values have a match, + test=ALLVALS + ) + +

SAS Macros

+ @li mf_existds.sas + @li mf_nobs.sas + @li mp_abort.sas + + + @param [in] indscol The input library.dataset.column to test for values + @param [in] checkvals= A library.dataset.column value containing a UNIQUE + list of values to be compared against the source (indscol). + @param [in] desc= (Testing observations) The user provided test description + @param [in] test= (ALLVALS) The test to apply. Valid values are: + @li ALLVALS - Test is a PASS if ALL values have a match in checkvals + @li ANYVAL - Test is a PASS if at least 1 value has a match in checkvals + @param [out] outds= (work.test_results) The output dataset to contain the + results. If it does not exist, it will be created, with the following format: + |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| + |---|---|---| + |User Provided description|PASS|Column &indscol contained ALL target vals| + + +

Related Macros

+ @li mp_assertdsobs.sas + + @version 9.2 + @author Allan Bowe + +**/ + +%macro mp_assertcolvals(indscol, + checkvals=0, + test=ALLVALS, + desc=mp_assertcolvals - no desc provided, + outds=work.test_results +)/*/STORE SOURCE*/; + + %mp_abort(iftrue= (&syscc ne 0) + ,mac=&sysmacroname + ,msg=%str(syscc=&syscc - on macro entry) + ) + + %local lib ds col clib cds ccol nobs; + %let lib=%scan(&indscol,1,%str(.)); + %let ds=%scan(&indscol,2,%str(.)); + %let col=%scan(&indscol,3,%str(.)); + %mp_abort(iftrue= (%mf_existds(&lib..&ds)=0) + ,mac=&sysmacroname + ,msg=%str(&lib..&ds not found!) + ) + + %mp_abort(iftrue= (&checkvals=0) + ,mac=&sysmacroname + ,msg=%str(Set CHECKVALS to a library.dataset.column containing check vals) + ) + %let clib=%scan(&checkvals,1,%str(.)); + %let cds=%scan(&checkvals,2,%str(.)); + %let ccol=%scan(&checkvals,3,%str(.)); + %mp_abort(iftrue= (%mf_existds(&clib..&cds)=0) + ,mac=&sysmacroname + ,msg=%str(&clib..&cds not found!) + ) + %let nobs=%mf_nobs(&clib..&cds); + %mp_abort(iftrue= (&nobs=0) + ,mac=&sysmacroname + ,msg=%str(&clib..&cds is empty!) + ) + + %let test=%upcase(&test); + + %if &test ne ALLVALS and &test ne ANYVAL %then %do; + %mp_abort( + mac=&sysmacroname, + msg=%str(Invalid test - &test) + ) + %end; + + %local result orig; + %let result=-1; + %let orig=-1; + proc sql noprint; + select count(*) into: result + from &lib..&ds + where &col not in ( + select &ccol from &clib..&cds + ); + select count(*) into: orig from &lib..&ds; + quit; + + %mp_abort(iftrue= (&syscc ne 0) + ,mac=&sysmacroname + ,msg=%str(syscc=&syscc after macro query) + ) + + data; + length test_description $256 test_result $4 test_comments $256; + test_description=symget('desc'); + test_result='FAIL'; + test_comments="&sysmacroname: &lib..&ds..&col has &result values " + !!"not in &clib..&cds..&ccol "; + %if &test=ANYVAL %then %do; + if &result < &orig then test_result='PASS'; + %end; + %else %if &test=ALLVALS %then %do; + if &result=0 then test_result='PASS'; + %end; + %else %do; + test_comments="&sysmacroname: Unsatisfied test condition - &test"; + %end; + run; + + %local ds; + %let ds=&syslast; + proc append base=&outds data=&ds; + run; + proc sql; + drop table &ds; + +%mend; \ No newline at end of file diff --git a/base/mp_assertdsobs.sas b/base/mp_assertdsobs.sas index 1769ffe..ea98e89 100644 --- a/base/mp_assertdsobs.sas +++ b/base/mp_assertdsobs.sas @@ -25,6 +25,8 @@ |---|---|---| |User Provided description|PASS|Dataset &inds has XX obs| +

Related Macros

+ @li mp_assertcolvals.sas @version 9.2 @author Allan Bowe diff --git a/tests/base/mp_assertcolvals.test.sas b/tests/base/mp_assertcolvals.test.sas new file mode 100644 index 0000000..f13d91d --- /dev/null +++ b/tests/base/mp_assertcolvals.test.sas @@ -0,0 +1,36 @@ +/** + @file + @brief Testing mp_assertcolvals macro + +

SAS Macros

+ @li mp_assertcolvals.sas + +**/ + + +data work.checkds; + do checkval='Jane','James','Jill'; + output; + end; +run; +%mp_assertcolvals(sashelp.class.name, + checkvals=work.checkds.checkval, + desc=At least one value has a match, + test=ANYVAL +) + +data work.check; + do val='M','F'; + output; + end; +run; +%mp_assertcolvals(sashelp.class.sex, + checkvals=work.check.val, + desc=All values have a match, + test=ALLVALS +) + + +%webout(OPEN) +%webout(OBJ, TEST_RESULTS) +%webout(CLOSE) \ No newline at end of file From d6235c63576f310262713334cc50f6398d614bf0 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Mon, 3 May 2021 11:17:20 +0300 Subject: [PATCH 51/70] chore: reducing raw_value size to 4000 for wider DB support --- all.sas | 4 ++-- base/mp_filtercheck.sas | 2 +- base/mp_filtergenerate.sas | 2 +- tests/base/mp_filtercheck.test.sas | 4 ++-- tests/base/mp_filtergenerate.test.sas | 10 +++++----- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/all.sas b/all.sas index 9e7265a..f14330e 100644 --- a/all.sas +++ b/all.sas @@ -3064,7 +3064,7 @@ run; The input table should have the following format: - |GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767| + |GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$4000| |---|---|---|---|---|---| |AND|AND|1|AGE|=|12| |AND|AND|1|SEX|<=|'M'| @@ -3212,7 +3212,7 @@ run; This feature is used to create dynamic dropdowns in [Data Controller for SAS®]( https://datacontroller.io). The input table should be in the format below: - |GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767| + |GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$4000| |---|---|---|---|---|---| |AND|AND|1|AGE|=|12| |AND|AND|1|SEX|<=|'M'| diff --git a/base/mp_filtercheck.sas b/base/mp_filtercheck.sas index 79343ac..62312d1 100644 --- a/base/mp_filtercheck.sas +++ b/base/mp_filtercheck.sas @@ -14,7 +14,7 @@ The input table should have the following format: - |GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767| + |GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$4000| |---|---|---|---|---|---| |AND|AND|1|AGE|=|12| |AND|AND|1|SEX|<=|'M'| diff --git a/base/mp_filtergenerate.sas b/base/mp_filtergenerate.sas index d6ef9f7..32f820a 100644 --- a/base/mp_filtergenerate.sas +++ b/base/mp_filtergenerate.sas @@ -5,7 +5,7 @@ This feature is used to create dynamic dropdowns in [Data Controller for SAS®]( https://datacontroller.io). The input table should be in the format below: - |GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767| + |GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$4000| |---|---|---|---|---|---| |AND|AND|1|AGE|=|12| |AND|AND|1|SEX|<=|'M'| diff --git a/tests/base/mp_filtercheck.test.sas b/tests/base/mp_filtercheck.test.sas index 5cce1b4..8ce727a 100644 --- a/tests/base/mp_filtercheck.test.sas +++ b/tests/base/mp_filtercheck.test.sas @@ -13,7 +13,7 @@ data work.inds; infile datalines4 dsd; input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. - OPERATOR_NM:$10. RAW_VALUE:$32767.; + OPERATOR_NM:$10. RAW_VALUE:$4000.; datalines4; AND,AND,1,AGE,=,12 AND,AND,1,SEX,<=,"'M'" @@ -39,7 +39,7 @@ run; data work.inds; infile datalines4 dsd; input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. - OPERATOR_NM:$10. RAW_VALUE:$32767.; + OPERATOR_NM:$10. RAW_VALUE:$4000.; datalines4; AND,AND,1,invalid,=,12 AND,AND,1,SEX,<=,"'M'" diff --git a/tests/base/mp_filtergenerate.test.sas b/tests/base/mp_filtergenerate.test.sas index be9e1c3..44bb673 100644 --- a/tests/base/mp_filtergenerate.test.sas +++ b/tests/base/mp_filtergenerate.test.sas @@ -15,7 +15,7 @@ options source2; data work.inds; infile datalines4 dsd; input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. - OPERATOR_NM:$10. RAW_VALUE:$32767.; + OPERATOR_NM:$10. RAW_VALUE:$4000.; datalines4; AND,AND,1,AGE,>,5 AND,AND,1,SEX,NE,"'M'" @@ -39,7 +39,7 @@ run; data work.inds; infile datalines4 dsd; input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. - OPERATOR_NM:$10. RAW_VALUE:$32767.; + OPERATOR_NM:$10. RAW_VALUE:$4000.; datalines4; ;;;; run; @@ -59,7 +59,7 @@ run; data work.inds; infile datalines4 dsd; input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. - OPERATOR_NM:$10. RAW_VALUE:$32767.; + OPERATOR_NM:$10. RAW_VALUE:$4000.; datalines4; AND,OR,2,Name,IN,"('Jane','Janet')" ;;;; @@ -80,7 +80,7 @@ run; data work.inds; infile datalines4 dsd; input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. - OPERATOR_NM:$10. RAW_VALUE:$32767.; + OPERATOR_NM:$10. RAW_VALUE:$4000.; datalines4; OR,OR,2,Name,IN,"('Jane','Janet')" OR,OR,3,Name,IN,"('James')" @@ -102,7 +102,7 @@ run; data work.inds; infile datalines4 dsd; input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. - OPERATOR_NM:$10. RAW_VALUE:$32767.; + OPERATOR_NM:$10. RAW_VALUE:$4000.; datalines4; AND,OR,2,Name,IN,"('Jane','Janet')" AND,OR,3,Name,IN,"('James')" From ff1eb54cc38727e09d30048424e2bae8336ffb5e Mon Sep 17 00:00:00 2001 From: allanbowe Date: Mon, 3 May 2021 10:59:25 +0200 Subject: [PATCH 52/70] chore: updating logo --- sasjs/doxy/Macro_core_website_1.png | Bin 58630 -> 149286 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/sasjs/doxy/Macro_core_website_1.png b/sasjs/doxy/Macro_core_website_1.png index 5c0a259e07a167d328b8535829b6e0e21213c3ca..130b9fa0554f3b37c81aff480c82e348a32e0246 100644 GIT binary patch literal 149286 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 z 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 ~@uNsbxpg2PS#r88(pgTY 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 a=|9u%Z*S&&S|C%%00f?{elF{r5}E)Cx^UM3 From 7f2ad5fc6698a20f88f424bfe39a02aed6c8467c Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Mon, 3 May 2021 13:48:24 +0300 Subject: [PATCH 53/70] feat: mp_filtervalidate.sas - to run a proc sql validate against the target table to ensure validity --- all.sas | 123 +++++++++++++++++++++++++- base/mp_filtercheck.sas | 16 +++- base/mp_filtergenerate.sas | 3 +- base/mp_filtervalidate.sas | 104 ++++++++++++++++++++++ tests/base/mp_filtercheck.test.sas | 6 +- tests/base/mp_filtervalidate.test.sas | 72 +++++++++++++++ 6 files changed, 317 insertions(+), 7 deletions(-) create mode 100644 base/mp_filtervalidate.sas create mode 100644 tests/base/mp_filtervalidate.test.sas diff --git a/all.sas b/all.sas index f14330e..f391764 100644 --- a/all.sas +++ b/all.sas @@ -3077,7 +3077,7 @@ run; @li SUBGROUP_LOGIC - only AND/OR @li SUBGROUP_ID - only integers @li VARIABLE_NM - must be in the target table - @li OPERATOR_NM - only =/>/=/BETWEEN/IN/NOT IN/NOT EQUAL/CONTAINS + @li OPERATOR_NM - only =/>/=/BETWEEN/IN/NOT IN/NE/CONTAINS @li RAW_VALUE - no unquoted values except integers, commas and spaces. @returns The &outds table containing any bad rows, plus a REASON_CD column. @@ -3092,11 +3092,15 @@ run;

SAS Macros

@li mp_abort.sas + @li mf_getuniquefileref.sas @li mf_getvarlist.sas @li mf_nobs.sas + @li mp_filtergenerate.sas + @li mp_filtervalidate.sas

Related Macros

@li mp_filtergenerate.sas + @li mp_filtervalidate.sas @version 9.3 @author Allan Bowe @@ -3200,9 +3204,19 @@ run; ) %end; %let syscc=1008; + %return; %end; +/** + * syntax checking passed but it does not mean the filter is valid + * for that we can run a proc sql validate query + */ +%local fref1; +%let fref1=%mf_getuniquefileref(); +%mp_filtergenerate(&inds,outref=&fref1) +/* this macro will also set syscc to 1008 if any issues found */ +%mp_filtervalidate(&fref1,&targetds,outds=&outds,abort=&abort) %mend; /** @@ -3228,7 +3242,7 @@ run; data work.filtertable; infile datalines4 dsd; input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. - OPERATOR_NM:$10. RAW_VALUE:$32767.; + OPERATOR_NM:$10. RAW_VALUE:$4000.; datalines4; AND,AND,1,AGE,=,12 AND,AND,1,SEX,<=,"'M'" @@ -3263,6 +3277,7 @@ run;

Related Macros

@li mp_filtercheck.sas + @li mp_filtervalidate.sas

SAS Macros

@li mp_abort.sas @@ -3304,6 +3319,110 @@ filename &outref temp; run; %end; +%mend; +/** + @file + @brief Checks a generated filter query for validity + @details Runs a generated filter in proc sql with the validate option. + Used in mp_filtercheck.sas in an fcmp container. + + Built to support dynamic filtering in + [Data Controller for SAS®](https://datacontroller.io). + + Usage: + + data work.filtertable; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$4000.; + datalines4; + AND,AND,1,AGE,=,12 + AND,AND,1,SEX,<=,"'M'" + AND,OR,2,Name,NOT IN,"('Jane','Alfred')" + AND,OR,2,Weight,>=,7 + ;;;; + run; + + %mp_filtergenerate(work.filtertable,outref=myfilter) + + %mp_filtervalidate(myfilter,sashelp.class) + + + @returns The SYSCC value will be 1008 if there are validation issues. + + @param [in] inref The input fileref to validate (generated by + mp_filtergenerate.sas) + @param [in] targetds The target dataset against which to verify the query + @param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions + @param [out] outds= (work.mp_filtervalidate) Output dataset containing the + error / warning message, if one exists. If this table contains any rows, + there are problems! + +

SAS Macros

+ @li mf_getuniquefileref.sas + @li mf_nobs.sas + @li mp_abort.sas + +

Related Macros

+ @li mp_filtercheck.sas + @li mp_filtergenerate.sas + + @version 9.3 + @author Allan Bowe + +**/ + +%macro mp_filtervalidate(inref,targetds,abort=YES,outds=work.mp_filtervalidate); + +%mp_abort(iftrue= (&syscc ne 0 or &syserr ne 0) + ,mac=&sysmacroname + ,msg=%str(syscc=&syscc / syserr=&syserr - on macro entry) +) + +%local fref1; +%let fref1=%mf_getuniquefileref(); + +data _null_; + file &fref1; + infile &inref end=eof; + if _n_=1 then do; + put "proc sql;"; + put "validate select * from &targetds"; + put "where " ; + end; + input; + put _infile_; + putlog _infile_; + if eof then put ";quit;"; +run; + +%inc &fref1; + +data &outds; + if &sqlrc or &syscc or &syserr then do; + REASON_CD=coalescec(symget('SYSERRORTEXT'),symget('SYSWARNINGTEXT')); + output; + end; + else stop; +run; + +filename &fref1 clear; + +%if %mf_nobs(&outds)>0 %then %do; + %if &abort=YES %then %do; + data _null_; + set &outds; + call symputx('REASON_CD',reason_cd,'l'); + stop; + run; + %mp_abort( + mac=&sysmacroname, + msg=%str(Filter issues in &inref: %quote(&reason_cd)) + ) + %end; + %let syscc=1008; +%end; + %mend; /** @file mp_getconstraints.sas diff --git a/base/mp_filtercheck.sas b/base/mp_filtercheck.sas index 62312d1..28d2873 100644 --- a/base/mp_filtercheck.sas +++ b/base/mp_filtercheck.sas @@ -27,7 +27,7 @@ @li SUBGROUP_LOGIC - only AND/OR @li SUBGROUP_ID - only integers @li VARIABLE_NM - must be in the target table - @li OPERATOR_NM - only =/>/=/BETWEEN/IN/NOT IN/NOT EQUAL/CONTAINS + @li OPERATOR_NM - only =/>/=/BETWEEN/IN/NOT IN/NE/CONTAINS @li RAW_VALUE - no unquoted values except integers, commas and spaces. @returns The &outds table containing any bad rows, plus a REASON_CD column. @@ -42,11 +42,15 @@

SAS Macros

@li mp_abort.sas + @li mf_getuniquefileref.sas @li mf_getvarlist.sas @li mf_nobs.sas + @li mp_filtergenerate.sas + @li mp_filtervalidate.sas

Related Macros

@li mp_filtergenerate.sas + @li mp_filtervalidate.sas @version 9.3 @author Allan Bowe @@ -150,8 +154,18 @@ run; ) %end; %let syscc=1008; + %return; %end; +/** + * syntax checking passed but it does not mean the filter is valid + * for that we can run a proc sql validate query + */ +%local fref1; +%let fref1=%mf_getuniquefileref(); +%mp_filtergenerate(&inds,outref=&fref1) +/* this macro will also set syscc to 1008 if any issues found */ +%mp_filtervalidate(&fref1,&targetds,outds=&outds,abort=&abort) %mend; diff --git a/base/mp_filtergenerate.sas b/base/mp_filtergenerate.sas index 32f820a..a0005e7 100644 --- a/base/mp_filtergenerate.sas +++ b/base/mp_filtergenerate.sas @@ -21,7 +21,7 @@ data work.filtertable; infile datalines4 dsd; input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. - OPERATOR_NM:$10. RAW_VALUE:$32767.; + OPERATOR_NM:$10. RAW_VALUE:$4000.; datalines4; AND,AND,1,AGE,=,12 AND,AND,1,SEX,<=,"'M'" @@ -56,6 +56,7 @@

Related Macros

@li mp_filtercheck.sas + @li mp_filtervalidate.sas

SAS Macros

@li mp_abort.sas diff --git a/base/mp_filtervalidate.sas b/base/mp_filtervalidate.sas new file mode 100644 index 0000000..cd8e0c4 --- /dev/null +++ b/base/mp_filtervalidate.sas @@ -0,0 +1,104 @@ +/** + @file + @brief Checks a generated filter query for validity + @details Runs a generated filter in proc sql with the validate option. + Used in mp_filtercheck.sas in an fcmp container. + + Built to support dynamic filtering in + [Data Controller for SAS®](https://datacontroller.io). + + Usage: + + data work.filtertable; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$4000.; + datalines4; + AND,AND,1,AGE,=,12 + AND,AND,1,SEX,<=,"'M'" + AND,OR,2,Name,NOT IN,"('Jane','Alfred')" + AND,OR,2,Weight,>=,7 + ;;;; + run; + + %mp_filtergenerate(work.filtertable,outref=myfilter) + + %mp_filtervalidate(myfilter,sashelp.class) + + + @returns The SYSCC value will be 1008 if there are validation issues. + + @param [in] inref The input fileref to validate (generated by + mp_filtergenerate.sas) + @param [in] targetds The target dataset against which to verify the query + @param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions + @param [out] outds= (work.mp_filtervalidate) Output dataset containing the + error / warning message, if one exists. If this table contains any rows, + there are problems! + +

SAS Macros

+ @li mf_getuniquefileref.sas + @li mf_nobs.sas + @li mp_abort.sas + +

Related Macros

+ @li mp_filtercheck.sas + @li mp_filtergenerate.sas + + @version 9.3 + @author Allan Bowe + +**/ + +%macro mp_filtervalidate(inref,targetds,abort=YES,outds=work.mp_filtervalidate); + +%mp_abort(iftrue= (&syscc ne 0 or &syserr ne 0) + ,mac=&sysmacroname + ,msg=%str(syscc=&syscc / syserr=&syserr - on macro entry) +) + +%local fref1; +%let fref1=%mf_getuniquefileref(); + +data _null_; + file &fref1; + infile &inref end=eof; + if _n_=1 then do; + put "proc sql;"; + put "validate select * from &targetds"; + put "where " ; + end; + input; + put _infile_; + putlog _infile_; + if eof then put ";quit;"; +run; + +%inc &fref1; + +data &outds; + if &sqlrc or &syscc or &syserr then do; + REASON_CD=coalescec(symget('SYSERRORTEXT'),symget('SYSWARNINGTEXT')); + output; + end; + else stop; +run; + +filename &fref1 clear; + +%if %mf_nobs(&outds)>0 %then %do; + %if &abort=YES %then %do; + data _null_; + set &outds; + call symputx('REASON_CD',reason_cd,'l'); + stop; + run; + %mp_abort( + mac=&sysmacroname, + msg=%str(Filter issues in &inref: %quote(&reason_cd)) + ) + %end; + %let syscc=1008; +%end; + +%mend; diff --git a/tests/base/mp_filtercheck.test.sas b/tests/base/mp_filtercheck.test.sas index 8ce727a..ce65277 100644 --- a/tests/base/mp_filtercheck.test.sas +++ b/tests/base/mp_filtercheck.test.sas @@ -63,7 +63,7 @@ run; data work.inds; infile datalines4 dsd; input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. - OPERATOR_NM:$10. RAW_VALUE:$32767.; + OPERATOR_NM:$10. RAW_VALUE:$4000.; datalines4; AND,OR,2,Name,NOT IN,"(''''Jane','Alfred')" ;;;; @@ -85,7 +85,7 @@ run; data work.inds; infile datalines4 dsd; input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. - OPERATOR_NM:$10. RAW_VALUE:$32767.; + OPERATOR_NM:$10. RAW_VALUE:$4000.; datalines4; AND,AND,1,%abort,=,12 AND,OR,2,Weight,>=,7 @@ -108,7 +108,7 @@ run; data work.inds; infile datalines4 dsd; input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. - OPERATOR_NM:$10. RAW_VALUE:$32767.; + OPERATOR_NM:$10. RAW_VALUE:$4000.; datalines4; AND,AND,1,age,=,;;%abort ;;;; diff --git a/tests/base/mp_filtervalidate.test.sas b/tests/base/mp_filtervalidate.test.sas new file mode 100644 index 0000000..7f1c4a6 --- /dev/null +++ b/tests/base/mp_filtervalidate.test.sas @@ -0,0 +1,72 @@ +/** + @file + @brief Testing mp_filtervalidate macro + +

SAS Macros

+ @li mp_filtergenerate.sas + @li mp_filtervalidate.sas + @li mp_assertdsobs.sas + +**/ + + +/* valid filter */ +data work.inds; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$4000.; +datalines4; +AND,AND,1,AGE,>,5 +AND,AND,1,SEX,NE,"'M'" +AND,OR,2,Name,NOT IN,"('Jane','Janet')" +AND,OR,2,Weight,>=,84.6 +;;;; +run; +%mp_filtergenerate(work.inds,outref=myfilter) +%mp_filtervalidate(myfilter,sashelp.class,outds=work.results,abort=NO) +%mp_assertdsobs(work.results, + desc=Valid filter, + test=EMPTY, + outds=work.test_results +) + +/* empty filter (return all records) */ +data work.inds; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$4000.; +datalines4; +;;;; +run; +%mp_filtergenerate(work.inds,outref=myfilter) +%mp_filtervalidate(myfilter,sashelp.class,outds=work.results,abort=NO) +%mp_assertdsobs(work.results, + desc=Valid filter, + test=EMPTY, + outds=work.test_results +) + + + +/* invalid filter*/ +data work.inds; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:$4000.; +datalines4; +AND,AND,1,SEX,NE,2 +;;;; +run; +%mp_filtergenerate(work.inds,outref=myfilter) +%mp_filtervalidate(myfilter,sashelp.class,outds=work.results,abort=NO) +%let syscc=0; +%mp_assertdsobs(work.results, + desc=Valid filter, + test=EQUALS 1, + outds=work.test_results +) + + +%webout(OPEN) +%webout(OBJ, TEST_RESULTS) +%webout(CLOSE) \ No newline at end of file From ffd2e135dc514590b3c48f8b0cb1d975f8e45f85 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Mon, 3 May 2021 20:28:48 +0300 Subject: [PATCH 54/70] fix: removing WARNINGs from code logic --- all.sas | 74 +++++++++++++++---------------- base/mf_existvarlist.sas | 2 +- base/mf_getattrc.sas | 2 +- base/mf_wordsinstr1butnotstr2.sas | 2 +- base/mp_ds2cards.sas | 4 +- base/mp_ds2csv.sas | 2 +- meta/mm_adduser2group.sas | 2 +- meta/mm_assigndirectlib.sas | 6 +-- meta/mm_createlibrary.sas | 8 ++-- meta/mm_createstp.sas | 28 ++++++------ meta/mm_deletedocument.sas | 2 +- meta/mm_getdocument.sas | 2 +- meta/mm_getwebappsrvprops.sas | 2 +- meta/mm_updateappextension.sas | 2 +- meta/mm_updatedocument.sas | 4 +- meta/mm_updatestpservertype.sas | 2 +- meta/mm_updatestpsourcecode.sas | 4 +- 17 files changed, 74 insertions(+), 74 deletions(-) diff --git a/all.sas b/all.sas index f391764..71b0196 100644 --- a/all.sas +++ b/all.sas @@ -325,7 +325,7 @@ options noquotelenmax; %let dsid=%sysfunc(open(&libds,is)); %if &dsid=0 %then %do; - %put WARNING: unable to open &libds in mf_existvarlist (&dsid); + %put %str(WARN)ING: unable to open &libds in mf_existvarlist (&dsid); %end; %if %sysfunc(attrn(&dsid,NVARS))=0 %then %do; @@ -377,7 +377,7 @@ options noquotelenmax; %local dsid rc; %let dsid=%sysfunc(open(&libds,is)); %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()); -1 %end; @@ -1602,7 +1602,7 @@ Usage: %local count_base count_extr i i2 extr_word base_word match outvar; %if %length(&str1)=0 or %length(&str2)=0 %then %do; - %put WARNING: empty string provided!; + %put %str(WARN)ING: empty string provided!; %put base string (str1)= &str1; %put compare string (str2) = &str2; %return; @@ -2783,7 +2783,7 @@ run; %local i setds nvars; %if not %sysfunc(exist(&base_ds)) %then %do; - %put WARNING: &base_ds does not exist; + %put %str(WARN)ING: &base_ds does not exist; %return; %end; @@ -2801,7 +2801,7 @@ select count(*) into: nvars from dictionary.columns where libname="%scan(%upcase(&base_ds),1)" and memname="%scan(%upcase(&base_ds),2)"; %if &nvars=0 %then %do; - %put WARNING: Dataset &base_ds has no variables! It will not be converted.; + %put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.; %return; %end; @@ -3012,7 +3012,7 @@ quit; )/*/STORE SOURCE*/; %if not %sysfunc(exist(&ds)) %then %do; - %put WARNING: &ds does not exist; + %put %str(WARN)ING: &ds does not exist; %return; %end; @@ -6256,7 +6256,7 @@ run; %end; %if &syscc ge 4 %then %do; - %put WARNING: SYSCC=&syscc, exiting &sysmacroname; + %put %str(WARN)ING: SYSCC=&syscc, exiting &sysmacroname; %return; %end; @@ -6615,7 +6615,7 @@ run; run; %if %length(&open_passthrough)>0 %then %do; - %put WARNING: Passthrough option for postgres not yet supported; + %put %str(WARN)ING: Passthrough option for postgres not yet supported; %return; %end; %else %do; @@ -6738,8 +6738,8 @@ run; %return; %end; %else %do; - %put WARNING: Engine &engine is currently unsupported; - %put WARNING- Please contact your support team.; + %put %str(WARN)ING: Engine &engine is currently unsupported; + %put %str(WARN)ING- Please contact your support team.; %return; %end; @@ -7431,7 +7431,7 @@ data _null_; putlog (_all_)(=); run; %if &checktype = SASLibrary %then %do; - %put WARNING: Library (&liburi) already exists with libname (&libname) ; + %put %str(WARN)ING: Library (&liburi) already exists with libname (&libname); %return; %end; @@ -7446,7 +7446,7 @@ data _null_; putlog (_all_)(=); run; %if &checktype = SASLibrary %then %do; - %put WARNING: Library (&liburi) already exists with libref (&libref) ; + %put %str(WARN)ING: Library (&liburi) already exists with libref (&libref) ; %return; %end; @@ -7466,7 +7466,7 @@ data _null_; call symputx('treeuri',uri,'l'); run; %if &foldertype ne Tree %then %do; - %put WARNING: Tree &tree does not exist!; + %put %str(WARN)ING: Tree &tree does not exist!; %return; %end; @@ -7574,7 +7574,7 @@ filename &frefout temp; * check SAS version */ %if %sysevalf(&sysver lt 9.3) %then %do; - %put WARNING: Version 9.3 or later required; + %put %str(WARN)ING: Version 9.3 or later required; %return; %end; @@ -7782,7 +7782,7 @@ data _null_; call symputx('treeuri',uri,'l'); run; %if &foldertype ne Tree %then %do; - %put WARNING: Tree &tree does not exist!; + %put %str(WARN)ING: Tree &tree does not exist!; %return; %end; @@ -7797,7 +7797,7 @@ data _null_; call symputx('stpuri',uri,'l'); run; %if &cmtype = ClassifierMap %then %do; - %put WARNING: Stored Process &stpname already exists in &tree!; + %put %str(WARN)ING: Stored Process &stpname already exists in &tree!; %return; %end; @@ -7805,14 +7805,14 @@ run; * Check that the physical file exists */ %if %sysfunc(fileexist(&directory/&filename)) ne 1 %then %do; - %put WARNING: FILE *&directory/&filename* NOT FOUND!; + %put %str(WARN)ING: FILE *&directory/&filename* NOT FOUND!; %return; %end; %if &stptype=1 %then %do; /* type 1 STP - where code is stored on filesystem */ %if %sysevalf(&sysver lt 9.2) %then %do; - %put WARNING: Version 9.2 or later required; + %put %str(WARN)ING: Version 9.2 or later required; %return; %end; @@ -7826,7 +7826,7 @@ run; %if &checkdirtype ne Directory %then %do; %mm_getdirectories(path=&directory,outds=&outds ,mDebug=&mDebug) %if %mf_nobs(&outds)=0 or %sysfunc(exist(&outds))=0 %then %do; - %put WARNING: The directory object does not exist for &directory; + %put %str(WARN)ING: The directory object does not exist for &directory; %return; %end; %end; @@ -7844,12 +7844,12 @@ run; length id $20 type $256; __rc=metadata_resolve("&treeuri",type,id); if type ne 'Tree' then do; - putlog "WARNING: Invalid tree URI: &treeuri"; + putlog "%str(WARN)ING: Invalid tree URI: &treeuri"; stopme=1; end; __rc=metadata_resolve(directoryuri,type,id); if type ne 'Directory' then do; - putlog 'WARNING: Invalid directory URI: ' directoryuri; + putlog "%str(WARN)ING: Invalid directory URI: " directoryuri; stopme=1; end; @@ -7858,7 +7858,7 @@ run; if type ne 'LogicalServer' then do; __rc=metadata_getnobj("omsobj:LogicalServer?@Name='&server'",1,serveruri); if serveruri='' then do; - putlog "WARNING: Invalid server: &server"; + putlog "%str(WARN)ING: Invalid server: &server"; stopme=1; end; end; @@ -7881,7 +7881,7 @@ run; rc6 = METADATA_SETATTR(prompturi, 'GroupInfo',groupinfo); if sum(of rc1-rc6) ne 0 then do; - putlog 'WARNING: Issue creating prompt.'; + putlog "%str(WARN)ING: Issue creating prompt."; if prompturi ne . then do; putlog ' Removing orphan: ' prompturi; rc = METADATA_DELOBJ(prompturi); @@ -7896,7 +7896,7 @@ run; rc9=METADATA_SETATTR(fileuri, 'IsARelativeName','1'); rc10=METADATA_SETASSN(fileuri, 'Directories','MODIFY',directoryuri); if sum(of rc7-rc10) ne 0 then do; - putlog 'WARNING: Issue creating file.'; + putlog "%str(WARN)ING: Issue creating file."; if fileuri ne . then do; putlog ' Removing orphans:' prompturi fileuri; rc = METADATA_DELOBJ(prompturi); @@ -7915,7 +7915,7 @@ run; !!""; rc14= METADATA_SETATTR(texturi, 'StoredText',storedtext); if sum(of rc11-rc14) ne 0 then do; - putlog 'WARNING: Issue creating TextStore.'; + putlog "%str(WARN)ING: Issue creating TextStore."; if texturi ne . then do; putlog ' Removing orphans: ' prompturi fileuri texturi; rc = METADATA_DELOBJ(prompturi); @@ -7963,7 +7963,7 @@ run; %else %if &stptype=2 %then %do; /* type 2 stp - code is stored in metadata */ %if %sysevalf(&sysver lt 9.3) %then %do; - %put WARNING: SAS version 9.3 or later required to create type2 STPs; + %put %str(WARN)ING: SAS version 9.3 or later required to create type2 STPs; %return; %end; /* check we have the correct ServerContext */ @@ -7975,7 +7975,7 @@ run; call symputx('serveruri',serveruri); run; %if &serveruri=NOTFOUND %then %do; - %put WARNING: ServerContext *&server* not found!; + %put %str(WARN)ING: ServerContext *&server* not found!; %return; %end; @@ -8046,7 +8046,7 @@ run; %end; %else %do; - %put WARNING: STPTYPE=*&stptype* not recognised!; + %put %str(WARN)ING: STPTYPE=*&stptype* not recognised!; %end; %mend;/** @@ -8493,7 +8493,7 @@ data _null_; call symputx('stpuri',uri,'l'); run; %if &type ne Document %then %do; - %put WARNING: No Document found at ⌖ + %put %str(WARN)ING: No Document found at ⌖ %return; %end; @@ -9107,7 +9107,7 @@ data _null_; when (' ') rec='0D'x; when ('$' ) rec='$' ; when (' ') rec='09'x; - otherwise putlog "WARNING: missing value for " entity=; + otherwise putlog "%str(WARN)ING: missing value for " entity=; end; rc =fput(fileid, substr(rec,1,1)); rc =fwrite(fileid); @@ -10907,7 +10907,7 @@ run; when (' ') rec='0D'x; when ('$' ) rec='$' ; when (' ') rec='09'x; - otherwise putlog "WARNING: missing value for " entity=; + otherwise putlog "%str(WARN)ING: missing value for " entity=; end; rc =fput(fileid, substr(rec,1,1)); rc =fwrite(fileid); @@ -11306,7 +11306,7 @@ data _null_; run; %if &appuri=stopifempty %then %do; - %put WARNING: &app.(Application) not found!; + %put %str(WARN)ING: &app.(Application) not found!; %return; %end; @@ -11427,12 +11427,12 @@ data _null_; run; %if &tsuri=stopifempty %then %do; - %put WARNING: &path/&name.(Document) not found!; + %put %str(WARN)ING: &path/&name.(Document) not found!; %return; %end; %if %length(&text)<2 %then %do; - %put WARNING: No text supplied!!; + %put %str(WARN)ING: No text supplied!!; %return; %end; @@ -11518,7 +11518,7 @@ data _null_; call symputx('stpuri',uri,'l'); run; %if &cmtype ne ClassifierMap %then %do; - %put WARNING: No Stored Process found at ⌖ + %put %str(WARN)ING: No Stored Process found at ⌖ %return; %end; @@ -11619,12 +11619,12 @@ data _null_; run; %if &tsuri=stopifempty %then %do; - %put WARNING: &stp.(StoredProcess) not found!; + %put %str(WARN)ING: &stp.(StoredProcess) not found!; %return; %end; %if %length(&stpcode)<2 %then %do; - %put WARNING: No SAS code supplied!!; + %put %str(WARN)ING: No SAS code supplied!!; %return; %end; diff --git a/base/mf_existvarlist.sas b/base/mf_existvarlist.sas index 721aadd..03e6aa3 100755 --- a/base/mf_existvarlist.sas +++ b/base/mf_existvarlist.sas @@ -29,7 +29,7 @@ %let dsid=%sysfunc(open(&libds,is)); %if &dsid=0 %then %do; - %put WARNING: unable to open &libds in mf_existvarlist (&dsid); + %put %str(WARN)ING: unable to open &libds in mf_existvarlist (&dsid); %end; %if %sysfunc(attrn(&dsid,NVARS))=0 %then %do; diff --git a/base/mf_getattrc.sas b/base/mf_getattrc.sas index 99eb78e..25a201d 100644 --- a/base/mf_getattrc.sas +++ b/base/mf_getattrc.sas @@ -23,7 +23,7 @@ %local dsid rc; %let dsid=%sysfunc(open(&libds,is)); %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()); -1 %end; diff --git a/base/mf_wordsinstr1butnotstr2.sas b/base/mf_wordsinstr1butnotstr2.sas index f2966f4..ec35930 100755 --- a/base/mf_wordsinstr1butnotstr2.sas +++ b/base/mf_wordsinstr1butnotstr2.sas @@ -30,7 +30,7 @@ %local count_base count_extr i i2 extr_word base_word match outvar; %if %length(&str1)=0 or %length(&str2)=0 %then %do; - %put WARNING: empty string provided!; + %put %str(WARN)ING: empty string provided!; %put base string (str1)= &str1; %put compare string (str2) = &str2; %return; diff --git a/base/mp_ds2cards.sas b/base/mp_ds2cards.sas index ac553ef..dd95b6d 100644 --- a/base/mp_ds2cards.sas +++ b/base/mp_ds2cards.sas @@ -43,7 +43,7 @@ %local i setds nvars; %if not %sysfunc(exist(&base_ds)) %then %do; - %put WARNING: &base_ds does not exist; + %put %str(WARN)ING: &base_ds does not exist; %return; %end; @@ -61,7 +61,7 @@ select count(*) into: nvars from dictionary.columns where libname="%scan(%upcase(&base_ds),1)" and memname="%scan(%upcase(&base_ds),2)"; %if &nvars=0 %then %do; - %put WARNING: Dataset &base_ds has no variables! It will not be converted.; + %put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.; %return; %end; diff --git a/base/mp_ds2csv.sas b/base/mp_ds2csv.sas index 30c95bb..8392a66 100644 --- a/base/mp_ds2csv.sas +++ b/base/mp_ds2csv.sas @@ -19,7 +19,7 @@ )/*/STORE SOURCE*/; %if not %sysfunc(exist(&ds)) %then %do; - %put WARNING: &ds does not exist; + %put %str(WARN)ING: &ds does not exist; %return; %end; diff --git a/meta/mm_adduser2group.sas b/meta/mm_adduser2group.sas index de18a23..7f40237 100644 --- a/meta/mm_adduser2group.sas +++ b/meta/mm_adduser2group.sas @@ -70,7 +70,7 @@ run; %end; %if &syscc ge 4 %then %do; - %put WARNING: SYSCC=&syscc, exiting &sysmacroname; + %put %str(WARN)ING: SYSCC=&syscc, exiting &sysmacroname; %return; %end; diff --git a/meta/mm_assigndirectlib.sas b/meta/mm_assigndirectlib.sas index 0f40f10..cb43576 100755 --- a/meta/mm_assigndirectlib.sas +++ b/meta/mm_assigndirectlib.sas @@ -332,7 +332,7 @@ run; run; %if %length(&open_passthrough)>0 %then %do; - %put WARNING: Passthrough option for postgres not yet supported; + %put %str(WARN)ING: Passthrough option for postgres not yet supported; %return; %end; %else %do; @@ -455,8 +455,8 @@ run; %return; %end; %else %do; - %put WARNING: Engine &engine is currently unsupported; - %put WARNING- Please contact your support team.; + %put %str(WARN)ING: Engine &engine is currently unsupported; + %put %str(WARN)ING- Please contact your support team.; %return; %end; diff --git a/meta/mm_createlibrary.sas b/meta/mm_createlibrary.sas index 8de4c0a..8bdd233 100644 --- a/meta/mm_createlibrary.sas +++ b/meta/mm_createlibrary.sas @@ -88,7 +88,7 @@ data _null_; putlog (_all_)(=); run; %if &checktype = SASLibrary %then %do; - %put WARNING: Library (&liburi) already exists with libname (&libname) ; + %put %str(WARN)ING: Library (&liburi) already exists with libname (&libname); %return; %end; @@ -103,7 +103,7 @@ data _null_; putlog (_all_)(=); run; %if &checktype = SASLibrary %then %do; - %put WARNING: Library (&liburi) already exists with libref (&libref) ; + %put %str(WARN)ING: Library (&liburi) already exists with libref (&libref) ; %return; %end; @@ -123,7 +123,7 @@ data _null_; call symputx('treeuri',uri,'l'); run; %if &foldertype ne Tree %then %do; - %put WARNING: Tree &tree does not exist!; + %put %str(WARN)ING: Tree &tree does not exist!; %return; %end; @@ -231,7 +231,7 @@ filename &frefout temp; * check SAS version */ %if %sysevalf(&sysver lt 9.3) %then %do; - %put WARNING: Version 9.3 or later required; + %put %str(WARN)ING: Version 9.3 or later required; %return; %end; diff --git a/meta/mm_createstp.sas b/meta/mm_createstp.sas index 53134b7..88c2f72 100755 --- a/meta/mm_createstp.sas +++ b/meta/mm_createstp.sas @@ -118,7 +118,7 @@ data _null_; call symputx('treeuri',uri,'l'); run; %if &foldertype ne Tree %then %do; - %put WARNING: Tree &tree does not exist!; + %put %str(WARN)ING: Tree &tree does not exist!; %return; %end; @@ -133,7 +133,7 @@ data _null_; call symputx('stpuri',uri,'l'); run; %if &cmtype = ClassifierMap %then %do; - %put WARNING: Stored Process &stpname already exists in &tree!; + %put %str(WARN)ING: Stored Process &stpname already exists in &tree!; %return; %end; @@ -141,14 +141,14 @@ run; * Check that the physical file exists */ %if %sysfunc(fileexist(&directory/&filename)) ne 1 %then %do; - %put WARNING: FILE *&directory/&filename* NOT FOUND!; + %put %str(WARN)ING: FILE *&directory/&filename* NOT FOUND!; %return; %end; %if &stptype=1 %then %do; /* type 1 STP - where code is stored on filesystem */ %if %sysevalf(&sysver lt 9.2) %then %do; - %put WARNING: Version 9.2 or later required; + %put %str(WARN)ING: Version 9.2 or later required; %return; %end; @@ -162,7 +162,7 @@ run; %if &checkdirtype ne Directory %then %do; %mm_getdirectories(path=&directory,outds=&outds ,mDebug=&mDebug) %if %mf_nobs(&outds)=0 or %sysfunc(exist(&outds))=0 %then %do; - %put WARNING: The directory object does not exist for &directory; + %put %str(WARN)ING: The directory object does not exist for &directory; %return; %end; %end; @@ -180,12 +180,12 @@ run; length id $20 type $256; __rc=metadata_resolve("&treeuri",type,id); if type ne 'Tree' then do; - putlog "WARNING: Invalid tree URI: &treeuri"; + putlog "%str(WARN)ING: Invalid tree URI: &treeuri"; stopme=1; end; __rc=metadata_resolve(directoryuri,type,id); if type ne 'Directory' then do; - putlog 'WARNING: Invalid directory URI: ' directoryuri; + putlog "%str(WARN)ING: Invalid directory URI: " directoryuri; stopme=1; end; @@ -194,7 +194,7 @@ run; if type ne 'LogicalServer' then do; __rc=metadata_getnobj("omsobj:LogicalServer?@Name='&server'",1,serveruri); if serveruri='' then do; - putlog "WARNING: Invalid server: &server"; + putlog "%str(WARN)ING: Invalid server: &server"; stopme=1; end; end; @@ -217,7 +217,7 @@ run; rc6 = METADATA_SETATTR(prompturi, 'GroupInfo',groupinfo); if sum(of rc1-rc6) ne 0 then do; - putlog 'WARNING: Issue creating prompt.'; + putlog "%str(WARN)ING: Issue creating prompt."; if prompturi ne . then do; putlog ' Removing orphan: ' prompturi; rc = METADATA_DELOBJ(prompturi); @@ -232,7 +232,7 @@ run; rc9=METADATA_SETATTR(fileuri, 'IsARelativeName','1'); rc10=METADATA_SETASSN(fileuri, 'Directories','MODIFY',directoryuri); if sum(of rc7-rc10) ne 0 then do; - putlog 'WARNING: Issue creating file.'; + putlog "%str(WARN)ING: Issue creating file."; if fileuri ne . then do; putlog ' Removing orphans:' prompturi fileuri; rc = METADATA_DELOBJ(prompturi); @@ -251,7 +251,7 @@ run; !!""; rc14= METADATA_SETATTR(texturi, 'StoredText',storedtext); if sum(of rc11-rc14) ne 0 then do; - putlog 'WARNING: Issue creating TextStore.'; + putlog "%str(WARN)ING: Issue creating TextStore."; if texturi ne . then do; putlog ' Removing orphans: ' prompturi fileuri texturi; rc = METADATA_DELOBJ(prompturi); @@ -299,7 +299,7 @@ run; %else %if &stptype=2 %then %do; /* type 2 stp - code is stored in metadata */ %if %sysevalf(&sysver lt 9.3) %then %do; - %put WARNING: SAS version 9.3 or later required to create type2 STPs; + %put %str(WARN)ING: SAS version 9.3 or later required to create type2 STPs; %return; %end; /* check we have the correct ServerContext */ @@ -311,7 +311,7 @@ run; call symputx('serveruri',serveruri); run; %if &serveruri=NOTFOUND %then %do; - %put WARNING: ServerContext *&server* not found!; + %put %str(WARN)ING: ServerContext *&server* not found!; %return; %end; @@ -382,7 +382,7 @@ run; %end; %else %do; - %put WARNING: STPTYPE=*&stptype* not recognised!; + %put %str(WARN)ING: STPTYPE=*&stptype* not recognised!; %end; %mend; \ No newline at end of file diff --git a/meta/mm_deletedocument.sas b/meta/mm_deletedocument.sas index 0abafae..a4bedab 100644 --- a/meta/mm_deletedocument.sas +++ b/meta/mm_deletedocument.sas @@ -32,7 +32,7 @@ data _null_; call symputx('stpuri',uri,'l'); run; %if &type ne Document %then %do; - %put WARNING: No Document found at ⌖ + %put %str(WARN)ING: No Document found at ⌖ %return; %end; diff --git a/meta/mm_getdocument.sas b/meta/mm_getdocument.sas index dc734da..4155672 100644 --- a/meta/mm_getdocument.sas +++ b/meta/mm_getdocument.sas @@ -125,7 +125,7 @@ data _null_; when (' ') rec='0D'x; when ('$' ) rec='$' ; when (' ') rec='09'x; - otherwise putlog "WARNING: missing value for " entity=; + otherwise putlog "%str(WARN)ING: missing value for " entity=; end; rc =fput(fileid, substr(rec,1,1)); rc =fwrite(fileid); diff --git a/meta/mm_getwebappsrvprops.sas b/meta/mm_getwebappsrvprops.sas index 57c1f22..7bdf001 100644 --- a/meta/mm_getwebappsrvprops.sas +++ b/meta/mm_getwebappsrvprops.sas @@ -99,7 +99,7 @@ run; when (' ') rec='0D'x; when ('$' ) rec='$' ; when (' ') rec='09'x; - otherwise putlog "WARNING: missing value for " entity=; + otherwise putlog "%str(WARN)ING: missing value for " entity=; end; rc =fput(fileid, substr(rec,1,1)); rc =fwrite(fileid); diff --git a/meta/mm_updateappextension.sas b/meta/mm_updateappextension.sas index 090bda3..710b7c6 100644 --- a/meta/mm_updateappextension.sas +++ b/meta/mm_updateappextension.sas @@ -70,7 +70,7 @@ data _null_; run; %if &appuri=stopifempty %then %do; - %put WARNING: &app.(Application) not found!; + %put %str(WARN)ING: &app.(Application) not found!; %return; %end; diff --git a/meta/mm_updatedocument.sas b/meta/mm_updatedocument.sas index da397a7..c376acb 100644 --- a/meta/mm_updatedocument.sas +++ b/meta/mm_updatedocument.sas @@ -58,12 +58,12 @@ data _null_; run; %if &tsuri=stopifempty %then %do; - %put WARNING: &path/&name.(Document) not found!; + %put %str(WARN)ING: &path/&name.(Document) not found!; %return; %end; %if %length(&text)<2 %then %do; - %put WARNING: No text supplied!!; + %put %str(WARN)ING: No text supplied!!; %return; %end; diff --git a/meta/mm_updatestpservertype.sas b/meta/mm_updatestpservertype.sas index 387d42f..9a2712f 100644 --- a/meta/mm_updatestpservertype.sas +++ b/meta/mm_updatestpservertype.sas @@ -34,7 +34,7 @@ data _null_; call symputx('stpuri',uri,'l'); run; %if &cmtype ne ClassifierMap %then %do; - %put WARNING: No Stored Process found at ⌖ + %put %str(WARN)ING: No Stored Process found at ⌖ %return; %end; diff --git a/meta/mm_updatestpsourcecode.sas b/meta/mm_updatestpsourcecode.sas index 4323575..bb8ffc9 100644 --- a/meta/mm_updatestpsourcecode.sas +++ b/meta/mm_updatestpsourcecode.sas @@ -68,12 +68,12 @@ data _null_; run; %if &tsuri=stopifempty %then %do; - %put WARNING: &stp.(StoredProcess) not found!; + %put %str(WARN)ING: &stp.(StoredProcess) not found!; %return; %end; %if %length(&stpcode)<2 %then %do; - %put WARNING: No SAS code supplied!!; + %put %str(WARN)ING: No SAS code supplied!!; %return; %end; From 1ef42d45af9c42c6e2228e26472fc941ae8c2e95 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Mon, 3 May 2021 22:43:56 +0300 Subject: [PATCH 55/70] fix: wrapping filter query in brackets to allow logic to be encapsulated when using with other logic sources --- all.sas | 3 ++- base/mp_filtergenerate.sas | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/all.sas b/all.sas index f391764..54a4686 100644 --- a/all.sas +++ b/all.sas @@ -3309,13 +3309,14 @@ filename &outref temp; file &outref lrecl=32800; set &inds end=last; by SUBGROUP_ID; - if _n_=1 then put '('; + if _n_=1 then put '(('; else if first.SUBGROUP_ID then put +1 GROUP_LOGIC '('; else put +2 SUBGROUP_LOGIC; put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE; if last.SUBGROUP_ID then put ')'@; + if last then put ')'; run; %end; diff --git a/base/mp_filtergenerate.sas b/base/mp_filtergenerate.sas index a0005e7..7654bf8 100644 --- a/base/mp_filtergenerate.sas +++ b/base/mp_filtergenerate.sas @@ -88,13 +88,14 @@ filename &outref temp; file &outref lrecl=32800; set &inds end=last; by SUBGROUP_ID; - if _n_=1 then put '('; + if _n_=1 then put '(('; else if first.SUBGROUP_ID then put +1 GROUP_LOGIC '('; else put +2 SUBGROUP_LOGIC; put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE; if last.SUBGROUP_ID then put ')'@; + if last then put ')'; run; %end; From e01b06b640e1deaeadf20bb369a8cbffe7034850 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Tue, 4 May 2021 21:53:57 +0300 Subject: [PATCH 56/70] feat: new assertion macro (mp_assertcols.sas) to test for column existence (or not) --- all.sas | 155 +++++++++++++++++++++++++++++++++++++-- base/mf_existvarlist.sas | 4 +- base/mp_assertcols.sas | 144 ++++++++++++++++++++++++++++++++++++ base/mp_zip.sas | 8 +- 4 files changed, 299 insertions(+), 12 deletions(-) create mode 100644 base/mp_assertcols.sas diff --git a/all.sas b/all.sas index 944e4a6..2d7f5ce 100644 --- a/all.sas +++ b/all.sas @@ -298,9 +298,9 @@ options noquotelenmax; @file @brief Checks if a set of variables ALL exist in a data set. @details Returns 0 if ANY of the variables do not exist, or 1 if they ALL do. - Usage: + Usage: - %put %mf_existVarList(sashelp.class, age sex name dummyvar) + %put %mf_existVarList(sashelp.class, age sex name dummyvar);

SAS Macros

@li mf_abort.sas @@ -1782,6 +1782,149 @@ Usage: %mend; /** @endcond *//** + @file + @brief Asserts the existence (or not) of columns + @details Useful in the context of writing sasjs tests. The results of the + test are _appended_ to the &outds. table. + + Example usage: + + %mp_assertcols(sashelp.class, + cols=name age sex, + test=ALL, + desc=check all columns exist + ) + + %mp_assertcols(sashelp.class, + cols=a b c, + test=NONE + ) + + %mp_assertcols(sashelp.class, + cols=age depth, + test=ANY + ) + +

SAS Macros

+ @li mf_existds.sas + @li mf_existvarlist.sas + @li mf_wordsinstr1butnotstr2.sas + @li mp_abort.sas + + + @param [in] inds The input library.dataset to test for values + @param [in] cols= The list of columns to check for + @param [in] desc= (Testing observations) The user provided test description + @param [in] test= (ALL) The test to apply. Valid values are: + @li ALL - Test is a PASS if ALL columns exist in &inds + @li ANY - Test is a PASS if ANY of the columns exist in &inds + @li NONE - Test is a PASS if NONE of the columns exist in &inds + @param [out] outds= (work.test_results) The output dataset to contain the + results. If it does not exist, it will be created, with the following format: + |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| + |---|---|---| + |User Provided description|PASS|Column &inds contained ALL columns| + + +

Related Macros

+ @li mp_assertdsobs.sas + @li mp_assertcolvals.sas + @li mp_assertdsobs.sas + + @version 9.2 + @author Allan Bowe + +**/ + +%macro mp_assertcols(inds, + cols=0, + test=ALL, + desc=0, + outds=work.test_results +)/*/STORE SOURCE*/; + + %mp_abort(iftrue= (&syscc ne 0) + ,mac=&sysmacroname + ,msg=%str(syscc=&syscc - on macro entry) + ) + + %local lib ds ; + %let lib=%scan(&inds,1,%str(.)); + %let ds=%scan(&inds,2,%str(.)); + %let cols=%upcase(&cols); + + %mp_abort(iftrue= (%mf_existds(&lib..&ds)=0) + ,mac=&sysmacroname + ,msg=%str(&lib..&ds not found!) + ) + + %mp_abort(iftrue= (&cols=0) + ,mac=&sysmacroname + ,msg=%str(No cols provided) + ) + + + %let test=%upcase(&test); + + %if &test ne ANY and &test ne ALL and &test ne NONE %then %do; + %mp_abort( + mac=&sysmacroname, + msg=%str(Invalid test - &test) + ) + %end; + + /** + * now do the actual test! + */ + %local result; + %if %mf_existVarList(&inds,&cols)=1 %then %let result=ALL; + %else %do; + %local targetcols compare; + %let targetcols=%upcase(%mf_getvarlist(&inds)); + %let compare=%mf_wordsinstr1butnotstr2( + Str1=&cols, + Str2=&targetcols + ); + %if %cmpres(&compare)=%cmpres(&cols) %then %let result=NONE; + %else %let result=SOME; + %end; + + data; + length test_description $256 test_result $4 test_comments $256; + test_description=symget('desc'); + if test_description='0' + then test_description="Testing &inds for existence of &test of: &cols"; + + test_result='FAIL'; + test_comments="&sysmacroname: &inds has &result columns "; + %if &test=ALL %then %do; + %if &result=ALL %then %do; + test_result='PASS'; + %end; + %end; + %else %if &test=ANY %then %do; + %if &result=SOME %then %do; + test_result='PASS'; + %end; + %end; + %else %if &test=NONE %then %do; + %if &result=NONE %then %do; + test_result='PASS'; + %end; + %end; + %else %do; + test_comments="&sysmacroname: Unsatisfied test condition - &test"; + %end; + run; + + %local ds; + %let ds=&syslast; + proc append base=&outds data=&ds; + run; + proc sql; + drop table &ds; + +%mend;/** @file @brief Asserts the values in a column @details Useful in the context of writing sasjs tests. The results of the @@ -6111,11 +6254,11 @@ alter table &libds modify &var char(&len); @file @brief Creates a zip file @details For DIRECTORY usage, will ignore subfolders. For DATASET usage, - provide a column that contains the full file path to each file to be zipped. + provide a column that contains the full file path to each file to be zipped. - %mp_zip(in=myzips,type=directory,outname=myDir) - %mp_zip(in=/my/file/path.txt,type=FILE,outname=myFile) - %mp_zip(in=SOMEDS,incol=FPATH,type=DATASET,outname=myFile) + %mp_zip(in=myzips,type=directory,outname=myDir) + %mp_zip(in=/my/file/path.txt,type=FILE,outname=myFile) + %mp_zip(in=SOMEDS,incol=FPATH,type=DATASET,outname=myFile) If you are sending zipped output to the _webout destination as part of an STP be sure that _debug is not set (else the SPWA will send non zipped content diff --git a/base/mf_existvarlist.sas b/base/mf_existvarlist.sas index 03e6aa3..7677d1e 100755 --- a/base/mf_existvarlist.sas +++ b/base/mf_existvarlist.sas @@ -2,9 +2,9 @@ @file @brief Checks if a set of variables ALL exist in a data set. @details Returns 0 if ANY of the variables do not exist, or 1 if they ALL do. - Usage: + Usage: - %put %mf_existVarList(sashelp.class, age sex name dummyvar) + %put %mf_existVarList(sashelp.class, age sex name dummyvar);

SAS Macros

@li mf_abort.sas diff --git a/base/mp_assertcols.sas b/base/mp_assertcols.sas new file mode 100644 index 0000000..6298b27 --- /dev/null +++ b/base/mp_assertcols.sas @@ -0,0 +1,144 @@ +/** + @file + @brief Asserts the existence (or not) of columns + @details Useful in the context of writing sasjs tests. The results of the + test are _appended_ to the &outds. table. + + Example usage: + + %mp_assertcols(sashelp.class, + cols=name age sex, + test=ALL, + desc=check all columns exist + ) + + %mp_assertcols(sashelp.class, + cols=a b c, + test=NONE + ) + + %mp_assertcols(sashelp.class, + cols=age depth, + test=ANY + ) + +

SAS Macros

+ @li mf_existds.sas + @li mf_existvarlist.sas + @li mf_wordsinstr1butnotstr2.sas + @li mp_abort.sas + + + @param [in] inds The input library.dataset to test for values + @param [in] cols= The list of columns to check for + @param [in] desc= (Testing observations) The user provided test description + @param [in] test= (ALL) The test to apply. Valid values are: + @li ALL - Test is a PASS if ALL columns exist in &inds + @li ANY - Test is a PASS if ANY of the columns exist in &inds + @li NONE - Test is a PASS if NONE of the columns exist in &inds + @param [out] outds= (work.test_results) The output dataset to contain the + results. If it does not exist, it will be created, with the following format: + |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| + |---|---|---| + |User Provided description|PASS|Column &inds contained ALL columns| + + +

Related Macros

+ @li mp_assertdsobs.sas + @li mp_assertcolvals.sas + @li mp_assertdsobs.sas + + @version 9.2 + @author Allan Bowe + +**/ + +%macro mp_assertcols(inds, + cols=0, + test=ALL, + desc=0, + outds=work.test_results +)/*/STORE SOURCE*/; + + %mp_abort(iftrue= (&syscc ne 0) + ,mac=&sysmacroname + ,msg=%str(syscc=&syscc - on macro entry) + ) + + %local lib ds ; + %let lib=%scan(&inds,1,%str(.)); + %let ds=%scan(&inds,2,%str(.)); + %let cols=%upcase(&cols); + + %mp_abort(iftrue= (%mf_existds(&lib..&ds)=0) + ,mac=&sysmacroname + ,msg=%str(&lib..&ds not found!) + ) + + %mp_abort(iftrue= (&cols=0) + ,mac=&sysmacroname + ,msg=%str(No cols provided) + ) + + + %let test=%upcase(&test); + + %if &test ne ANY and &test ne ALL and &test ne NONE %then %do; + %mp_abort( + mac=&sysmacroname, + msg=%str(Invalid test - &test) + ) + %end; + + /** + * now do the actual test! + */ + %local result; + %if %mf_existVarList(&inds,&cols)=1 %then %let result=ALL; + %else %do; + %local targetcols compare; + %let targetcols=%upcase(%mf_getvarlist(&inds)); + %let compare=%mf_wordsinstr1butnotstr2( + Str1=&cols, + Str2=&targetcols + ); + %if %cmpres(&compare)=%cmpres(&cols) %then %let result=NONE; + %else %let result=SOME; + %end; + + data; + length test_description $256 test_result $4 test_comments $256; + test_description=symget('desc'); + if test_description='0' + then test_description="Testing &inds for existence of &test of: &cols"; + + test_result='FAIL'; + test_comments="&sysmacroname: &inds has &result columns "; + %if &test=ALL %then %do; + %if &result=ALL %then %do; + test_result='PASS'; + %end; + %end; + %else %if &test=ANY %then %do; + %if &result=SOME %then %do; + test_result='PASS'; + %end; + %end; + %else %if &test=NONE %then %do; + %if &result=NONE %then %do; + test_result='PASS'; + %end; + %end; + %else %do; + test_comments="&sysmacroname: Unsatisfied test condition - &test"; + %end; + run; + + %local ds; + %let ds=&syslast; + proc append base=&outds data=&ds; + run; + proc sql; + drop table &ds; + +%mend; \ No newline at end of file diff --git a/base/mp_zip.sas b/base/mp_zip.sas index 45b8c06..97377a9 100644 --- a/base/mp_zip.sas +++ b/base/mp_zip.sas @@ -2,11 +2,11 @@ @file @brief Creates a zip file @details For DIRECTORY usage, will ignore subfolders. For DATASET usage, - provide a column that contains the full file path to each file to be zipped. + provide a column that contains the full file path to each file to be zipped. - %mp_zip(in=myzips,type=directory,outname=myDir) - %mp_zip(in=/my/file/path.txt,type=FILE,outname=myFile) - %mp_zip(in=SOMEDS,incol=FPATH,type=DATASET,outname=myFile) + %mp_zip(in=myzips,type=directory,outname=myDir) + %mp_zip(in=/my/file/path.txt,type=FILE,outname=myFile) + %mp_zip(in=SOMEDS,incol=FPATH,type=DATASET,outname=myFile) If you are sending zipped output to the _webout destination as part of an STP be sure that _debug is not set (else the SPWA will send non zipped content From fb21a0adfd0e18c6190fa1ad59301b4f1f679902 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Wed, 5 May 2021 01:35:00 +0300 Subject: [PATCH 57/70] feat: new macro for validating inputs (mp_validatecol.sas), also a refresh of the tests now that sasjs test is released. All tests are passing --- base/mp_validatecol.sas | 65 + sasjs/sasjsconfig.json | 26 +- sasjsresults/testResults.csv | 352 +++++ sasjsresults/testResults.json | 1825 +++++++++++++++++++++++ tests/base/mp_assertcolvals.test.sas | 4 - tests/base/mp_filtercheck.test.sas | 5 - tests/base/mp_filtergenerate.test.sas | 4 - tests/base/mp_filtervalidate.test.sas | 4 - tests/base/mp_validatecol.test.sas | 62 + tests/testterm.sas | 9 + tests/viya/mv_createwebservice.test.sas | 19 +- 11 files changed, 2347 insertions(+), 28 deletions(-) create mode 100644 base/mp_validatecol.sas create mode 100644 sasjsresults/testResults.csv create mode 100644 sasjsresults/testResults.json create mode 100644 tests/base/mp_validatecol.test.sas create mode 100644 tests/testterm.sas diff --git a/base/mp_validatecol.sas b/base/mp_validatecol.sas new file mode 100644 index 0000000..147919a --- /dev/null +++ b/base/mp_validatecol.sas @@ -0,0 +1,65 @@ +/** + @file + @brief Used to validate variables in a dataset + @details Useful when sanitising inputs, to ensure that they arrive with a + certain pattern. + Usage: + + data test; + infile datalines4 dsd; + input; + libds=_infile_; + %mp_validatecol(libds,LIBDS,is_libds) + datalines4; + some.libname + !lib.blah + %abort + definite.ok + not.ok! + nineletrs._ + ;;;; + run; + + @param [in] incol The column to be validated + @param [in] rule The rule to apply. Current rules: + @li LIBDS - matches LIBREF.DATASET format + @param [out] outcol The variable to create, with the results of the match + +

SAS Macros

+ @li mf_getuniquename.sas + + @version 9.3 +**/ + +%macro mp_validatecol(incol,rule,outcol); + +/* tempcol is given a unique name with every invocation */ +%local tempcol; +%let tempcol=%mf_getuniquename(); + +%if &rule=ISNUM %then %do; + /* + credit SØREN LASSEN + https://sasmacro.blogspot.com/2009/06/welcome-isnum-macro.html + */ + &tempcol=input(&incol,?? best32.); + if missing(&tempcol) then &outcol=0; + else &outcol=1; + drop &tempcol; +%end; +%else %if &rule=LIBDS %then %do; + /* match libref.dataset */ + if _n_=1 then do; + retain &tempcol; + &tempcol=prxparse('/^[_a-z]\w{0,7}\.[_a-z]\w{0,31}$/i'); + if missing(&tempcol) then do; + putlog "%str(ERR)OR: Invalid expression for LIBDS"; + stop; + end; + drop &tempcol; + end; + if prxmatch(&tempcol, trim(&incol)) then &outcol=1; + else &outcol=0; +%end; + +%mend; diff --git a/sasjs/sasjsconfig.json b/sasjs/sasjsconfig.json index 2e10dd2..e3432bc 100644 --- a/sasjs/sasjsconfig.json +++ b/sasjs/sasjsconfig.json @@ -17,7 +17,22 @@ } }, "serviceConfig": { - "initProgram": "tests/testinit.sas" + "initProgram": "tests/testinit.sas", + "termProgram": "tests/testterm.sas", + "serviceFolders": [ + "tests/base", + "tests/viya" + ], + "macroVars": { + "mcTestAppLoc": "/Public/temp/macrocore" + } + }, + "testConfig": { + "initProgram": "tests/testinit.sas", + "termProgram": "tests/testterm.sas", + "macroVars": { + "mcTestAppLoc": "/Public/temp/macrocore" + } }, "defaultTarget": "viya", "targets": [ @@ -26,15 +41,6 @@ "serverUrl": "https://sas.analytium.co.uk", "serverType": "SASVIYA", "appLoc": "/Public/temp/macrocore", - "serviceConfig": { - "serviceFolders": [ - "tests/base", - "tests/viya" - ], - "macroVars": { - "mcTestAppLoc": "/Public/temp/macrocore" - } - }, "deployConfig": { "deployServicePack": true }, diff --git a/sasjsresults/testResults.csv b/sasjsresults/testResults.csv new file mode 100644 index 0000000..7fc341a --- /dev/null +++ b/sasjsresults/testResults.csv @@ -0,0 +1,352 @@ +test_target,test_loc,sasjs_test_id,test_suite_result,test_description +mp_assertcolvals,tests/services/base/mp_assertcolvals.test.sas,effe793c-9f51-4b15-b935-03b9c46c05ca,PASS,At least one value has a match +mp_assertcolvals,tests/services/base/mp_assertcolvals.test.sas,effe793c-9f51-4b15-b935-03b9c46c05ca,PASS,All values have a match +mp_filtercheck,tests/services/base/mp_filtercheck.test.sas,d50073ee-e648-4ae2-a948-8b1fb63cf110,PASS,Valid filter query +mp_filtercheck,tests/services/base/mp_filtercheck.test.sas,d50073ee-e648-4ae2-a948-8b1fb63cf110,PASS,Invalid column name +mp_filtercheck,tests/services/base/mp_filtercheck.test.sas,d50073ee-e648-4ae2-a948-8b1fb63cf110,PASS,Invalid raw value +mp_filtercheck,tests/services/base/mp_filtercheck.test.sas,d50073ee-e648-4ae2-a948-8b1fb63cf110,PASS,Code injection - column name +mp_filtercheck,tests/services/base/mp_filtercheck.test.sas,d50073ee-e648-4ae2-a948-8b1fb63cf110,PASS,Code injection - raw value abort +mp_filtergenerate,tests/services/base/mp_filtergenerate.test.sas,e25543aa-1070-4a13-9df9-95cfbc279708,PASS,Valid filter +mp_filtergenerate,tests/services/base/mp_filtergenerate.test.sas,e25543aa-1070-4a13-9df9-95cfbc279708,PASS,Empty filter (return all records) +mp_filtergenerate,tests/services/base/mp_filtergenerate.test.sas,e25543aa-1070-4a13-9df9-95cfbc279708,PASS,Single line filter +mp_filtergenerate,tests/services/base/mp_filtergenerate.test.sas,e25543aa-1070-4a13-9df9-95cfbc279708,PASS,Single line 2 group filter +mp_filtergenerate,tests/services/base/mp_filtergenerate.test.sas,e25543aa-1070-4a13-9df9-95cfbc279708,PASS,Filter with nothing returned +mp_filtervalidate,tests/services/base/mp_filtervalidate.test.sas,cb8df59b-fedb-40de-9590-f1aa948756b5,PASS,Valid filter +mp_filtervalidate,tests/services/base/mp_filtervalidate.test.sas,cb8df59b-fedb-40de-9590-f1aa948756b5,PASS,Valid filter +mp_filtervalidate,tests/services/base/mp_filtervalidate.test.sas,cb8df59b-fedb-40de-9590-f1aa948756b5,PASS,Valid filter +mp_validatecol,tests/services/base/mp_validatecol.test.sas,307f95c2-83b4-4caa-8b2f-ba1a673d6ff4,FAIL,Testing LIBDS +mp_validatecol,tests/services/base/mp_validatecol.test.sas,307f95c2-83b4-4caa-8b2f-ba1a673d6ff4,PASS,Test2 - ISNUM +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character +mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,PASS,Creating web service with invisible character diff --git a/sasjsresults/testResults.json b/sasjsresults/testResults.json new file mode 100644 index 0000000..b3441cc --- /dev/null +++ b/sasjsresults/testResults.json @@ -0,0 +1,1825 @@ +{ + "sasjs_test_meta": [ + { + "test_target": "mp_assertcolvals", + "results": [ + { + "test_loc": "tests/services/base/mp_assertcolvals.test.sas", + "sasjs_test_id": "effe793c-9f51-4b15-b935-03b9c46c05ca", + "result": [ + { + "TEST_DESCRIPTION": "At least one value has a match", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTCOLVALS: sashelp.class.name has 18 values not in work.checkds.checkval" + }, + { + "TEST_DESCRIPTION": "All values have a match", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTCOLVALS: sashelp.class.sex has 0 values not in work.check.val" + } + ] + } + ] + }, + { + "test_target": "mp_filtercheck", + "results": [ + { + "test_loc": "tests/services/base/mp_filtercheck.test.sas", + "sasjs_test_id": "d50073ee-e648-4ae2-a948-8b1fb63cf110", + "result": [ + { + "TEST_DESCRIPTION": "Valid filter query", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.badrecords has 0 observations" + }, + { + "TEST_DESCRIPTION": "Invalid column name", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.badrecords has 1 observations" + }, + { + "TEST_DESCRIPTION": "Invalid raw value", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.badrecords has 1 observations" + }, + { + "TEST_DESCRIPTION": "Code injection - column name", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.badrecords has 1 observations" + }, + { + "TEST_DESCRIPTION": "Code injection - raw value abort", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.badrecords has 1 observations" + } + ] + } + ] + }, + { + "test_target": "mp_filtergenerate", + "results": [ + { + "test_loc": "tests/services/base/mp_filtergenerate.test.sas", + "sasjs_test_id": "e25543aa-1070-4a13-9df9-95cfbc279708", + "result": [ + { + "TEST_DESCRIPTION": "Valid filter", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.test has 8 observations" + }, + { + "TEST_DESCRIPTION": "Empty filter (return all records)", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.test has 19 observations" + }, + { + "TEST_DESCRIPTION": "Single line filter", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.test has 2 observations" + }, + { + "TEST_DESCRIPTION": "Single line 2 group filter", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.test has 3 observations" + }, + { + "TEST_DESCRIPTION": "Filter with nothing returned", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.test has 0 observations" + } + ] + } + ] + }, + { + "test_target": "mp_filtervalidate", + "results": [ + { + "test_loc": "tests/services/base/mp_filtervalidate.test.sas", + "sasjs_test_id": "cb8df59b-fedb-40de-9590-f1aa948756b5", + "result": [ + { + "TEST_DESCRIPTION": "Valid filter", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.results has 0 observations" + }, + { + "TEST_DESCRIPTION": "Valid filter", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.results has 0 observations" + }, + { + "TEST_DESCRIPTION": "Valid filter", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.results has 1 observations" + } + ] + } + ] + }, + { + "test_target": "mp_validatecol", + "results": [ + { + "test_loc": "tests/services/base/mp_validatecol.test.sas", + "sasjs_test_id": "307f95c2-83b4-4caa-8b2f-ba1a673d6ff4", + "result": [ + { + "TEST_DESCRIPTION": "Testing LIBDS", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.test1 has 0 observations" + }, + { + "TEST_DESCRIPTION": "Test2 - ISNUM", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.test2 has 4 observations" + } + ] + } + ] + }, + { + "test_target": "mv_createwebservice", + "results": [ + { + "test_loc": "tests/services/viya/mv_createwebservice.test.sas", + "sasjs_test_id": "1eb31d67-d56e-4d7b-aa14-3f2d21fa3545", + "result": [ + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "FAIL", + "TEST_COMMENTS": "" + }, + { + "TEST_DESCRIPTION": "Creating web service with invisible character", + "TEST_RESULT": "PASS", + "TEST_COMMENTS": "" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/base/mp_assertcolvals.test.sas b/tests/base/mp_assertcolvals.test.sas index f13d91d..9640b8a 100644 --- a/tests/base/mp_assertcolvals.test.sas +++ b/tests/base/mp_assertcolvals.test.sas @@ -30,7 +30,3 @@ run; test=ALLVALS ) - -%webout(OPEN) -%webout(OBJ, TEST_RESULTS) -%webout(CLOSE) \ No newline at end of file diff --git a/tests/base/mp_filtercheck.test.sas b/tests/base/mp_filtercheck.test.sas index ce65277..45048a7 100644 --- a/tests/base/mp_filtercheck.test.sas +++ b/tests/base/mp_filtercheck.test.sas @@ -125,8 +125,3 @@ run; outds=work.test_results ) - - -%webout(OPEN) -%webout(OBJ, TEST_RESULTS) -%webout(CLOSE) \ No newline at end of file diff --git a/tests/base/mp_filtergenerate.test.sas b/tests/base/mp_filtergenerate.test.sas index 44bb673..5464b84 100644 --- a/tests/base/mp_filtergenerate.test.sas +++ b/tests/base/mp_filtergenerate.test.sas @@ -120,7 +120,3 @@ run; outds=work.test_results ) - -%webout(OPEN) -%webout(OBJ, TEST_RESULTS) -%webout(CLOSE) \ No newline at end of file diff --git a/tests/base/mp_filtervalidate.test.sas b/tests/base/mp_filtervalidate.test.sas index 7f1c4a6..f8837e4 100644 --- a/tests/base/mp_filtervalidate.test.sas +++ b/tests/base/mp_filtervalidate.test.sas @@ -66,7 +66,3 @@ run; outds=work.test_results ) - -%webout(OPEN) -%webout(OBJ, TEST_RESULTS) -%webout(CLOSE) \ No newline at end of file diff --git a/tests/base/mp_validatecol.test.sas b/tests/base/mp_validatecol.test.sas new file mode 100644 index 0000000..b093d47 --- /dev/null +++ b/tests/base/mp_validatecol.test.sas @@ -0,0 +1,62 @@ +/** + @file + @brief Testing mp_validatecol.sas macro + +

SAS Macros

+ @li mp_assertdsobs.sas + @li mp_validatecol.sas + +**/ + + +/** + * Test 1 - LIBDS + */ +data test1; + infile datalines4 dsd; + input; + libds=_infile_; + %mp_validatecol(libds,LIBDS,is_libds) + if libds=1; +datalines4; +some.libname +!lib.blah +%abort +definite.ok +not.ok! +nineletrs._ +;;;; +run; +%mp_assertdsobs(work.test1, + desc=Testing LIBDS, + test=EQUALS 2, + outds=work.test_results +) + +/** + * Test 2 - ISNUM + */ +data test2; + infile datalines4 dsd; + input; + infile=_infile_; + %mp_validatecol(infile,ISNUM,is_numeric) + if is_numeric=1; +datalines4; +1 +0001 +1e6 +-44 +above are good +the rest are bad +%abort +1&somethingverybad. +& ++-1 +;;;; +run; +%mp_assertdsobs(work.test2, + desc=Test2 - ISNUM, + test=EQUALS 4, + outds=work.test_results +) \ No newline at end of file diff --git a/tests/testterm.sas b/tests/testterm.sas new file mode 100644 index 0000000..2e5deb7 --- /dev/null +++ b/tests/testterm.sas @@ -0,0 +1,9 @@ +/** + @file + @brief term file for tests + +**/ + +%webout(OPEN) +%webout(OBJ, TEST_RESULTS) +%webout(CLOSE) \ No newline at end of file diff --git a/tests/viya/mv_createwebservice.test.sas b/tests/viya/mv_createwebservice.test.sas index 4abee82..2c8f8e7 100644 --- a/tests/viya/mv_createwebservice.test.sas +++ b/tests/viya/mv_createwebservice.test.sas @@ -4,6 +4,7 @@

SAS Macros

@li mv_createwebservice.sas + @li mv_getjobcode.sas **/ @@ -21,4 +22,20 @@ run; path=&mcTestAppLoc/tests/macros, code=testref, name=mv_createwebservice -) \ No newline at end of file +) + +filename compare temp; +%mv_getjobcode( + path=&mcTestAppLoc/tests/macros + ,name=mv_createwebservice + ,outref=compare; +) + +data test_results; + length test_description $256 test_result $4 test_comments $256; + infile compare; + input; + if _infile_='01'x then test_result='PASS'; + else test_result='FAIL'; + test_description="Creating web service with invisible character"; +run; \ No newline at end of file From 4198448b814004f9002eb519cae39414f0ea8030 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Wed, 5 May 2021 01:39:29 +0300 Subject: [PATCH 58/70] chore: removing temp results folder --- sasjsresults/testResults.csv | 352 ------- sasjsresults/testResults.json | 1825 --------------------------------- 2 files changed, 2177 deletions(-) delete mode 100644 sasjsresults/testResults.csv delete mode 100644 sasjsresults/testResults.json diff --git a/sasjsresults/testResults.csv b/sasjsresults/testResults.csv deleted file mode 100644 index 7fc341a..0000000 --- a/sasjsresults/testResults.csv +++ /dev/null @@ -1,352 +0,0 @@ -test_target,test_loc,sasjs_test_id,test_suite_result,test_description -mp_assertcolvals,tests/services/base/mp_assertcolvals.test.sas,effe793c-9f51-4b15-b935-03b9c46c05ca,PASS,At least one value has a match -mp_assertcolvals,tests/services/base/mp_assertcolvals.test.sas,effe793c-9f51-4b15-b935-03b9c46c05ca,PASS,All values have a match -mp_filtercheck,tests/services/base/mp_filtercheck.test.sas,d50073ee-e648-4ae2-a948-8b1fb63cf110,PASS,Valid filter query -mp_filtercheck,tests/services/base/mp_filtercheck.test.sas,d50073ee-e648-4ae2-a948-8b1fb63cf110,PASS,Invalid column name -mp_filtercheck,tests/services/base/mp_filtercheck.test.sas,d50073ee-e648-4ae2-a948-8b1fb63cf110,PASS,Invalid raw value -mp_filtercheck,tests/services/base/mp_filtercheck.test.sas,d50073ee-e648-4ae2-a948-8b1fb63cf110,PASS,Code injection - column name -mp_filtercheck,tests/services/base/mp_filtercheck.test.sas,d50073ee-e648-4ae2-a948-8b1fb63cf110,PASS,Code injection - raw value abort -mp_filtergenerate,tests/services/base/mp_filtergenerate.test.sas,e25543aa-1070-4a13-9df9-95cfbc279708,PASS,Valid filter -mp_filtergenerate,tests/services/base/mp_filtergenerate.test.sas,e25543aa-1070-4a13-9df9-95cfbc279708,PASS,Empty filter (return all records) -mp_filtergenerate,tests/services/base/mp_filtergenerate.test.sas,e25543aa-1070-4a13-9df9-95cfbc279708,PASS,Single line filter -mp_filtergenerate,tests/services/base/mp_filtergenerate.test.sas,e25543aa-1070-4a13-9df9-95cfbc279708,PASS,Single line 2 group filter -mp_filtergenerate,tests/services/base/mp_filtergenerate.test.sas,e25543aa-1070-4a13-9df9-95cfbc279708,PASS,Filter with nothing returned -mp_filtervalidate,tests/services/base/mp_filtervalidate.test.sas,cb8df59b-fedb-40de-9590-f1aa948756b5,PASS,Valid filter -mp_filtervalidate,tests/services/base/mp_filtervalidate.test.sas,cb8df59b-fedb-40de-9590-f1aa948756b5,PASS,Valid filter -mp_filtervalidate,tests/services/base/mp_filtervalidate.test.sas,cb8df59b-fedb-40de-9590-f1aa948756b5,PASS,Valid filter -mp_validatecol,tests/services/base/mp_validatecol.test.sas,307f95c2-83b4-4caa-8b2f-ba1a673d6ff4,FAIL,Testing LIBDS -mp_validatecol,tests/services/base/mp_validatecol.test.sas,307f95c2-83b4-4caa-8b2f-ba1a673d6ff4,PASS,Test2 - ISNUM -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,FAIL,Creating web service with invisible character -mv_createwebservice,tests/services/viya/mv_createwebservice.test.sas,1eb31d67-d56e-4d7b-aa14-3f2d21fa3545,PASS,Creating web service with invisible character diff --git a/sasjsresults/testResults.json b/sasjsresults/testResults.json deleted file mode 100644 index b3441cc..0000000 --- a/sasjsresults/testResults.json +++ /dev/null @@ -1,1825 +0,0 @@ -{ - "sasjs_test_meta": [ - { - "test_target": "mp_assertcolvals", - "results": [ - { - "test_loc": "tests/services/base/mp_assertcolvals.test.sas", - "sasjs_test_id": "effe793c-9f51-4b15-b935-03b9c46c05ca", - "result": [ - { - "TEST_DESCRIPTION": "At least one value has a match", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTCOLVALS: sashelp.class.name has 18 values not in work.checkds.checkval" - }, - { - "TEST_DESCRIPTION": "All values have a match", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTCOLVALS: sashelp.class.sex has 0 values not in work.check.val" - } - ] - } - ] - }, - { - "test_target": "mp_filtercheck", - "results": [ - { - "test_loc": "tests/services/base/mp_filtercheck.test.sas", - "sasjs_test_id": "d50073ee-e648-4ae2-a948-8b1fb63cf110", - "result": [ - { - "TEST_DESCRIPTION": "Valid filter query", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.badrecords has 0 observations" - }, - { - "TEST_DESCRIPTION": "Invalid column name", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.badrecords has 1 observations" - }, - { - "TEST_DESCRIPTION": "Invalid raw value", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.badrecords has 1 observations" - }, - { - "TEST_DESCRIPTION": "Code injection - column name", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.badrecords has 1 observations" - }, - { - "TEST_DESCRIPTION": "Code injection - raw value abort", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.badrecords has 1 observations" - } - ] - } - ] - }, - { - "test_target": "mp_filtergenerate", - "results": [ - { - "test_loc": "tests/services/base/mp_filtergenerate.test.sas", - "sasjs_test_id": "e25543aa-1070-4a13-9df9-95cfbc279708", - "result": [ - { - "TEST_DESCRIPTION": "Valid filter", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.test has 8 observations" - }, - { - "TEST_DESCRIPTION": "Empty filter (return all records)", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.test has 19 observations" - }, - { - "TEST_DESCRIPTION": "Single line filter", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.test has 2 observations" - }, - { - "TEST_DESCRIPTION": "Single line 2 group filter", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.test has 3 observations" - }, - { - "TEST_DESCRIPTION": "Filter with nothing returned", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.test has 0 observations" - } - ] - } - ] - }, - { - "test_target": "mp_filtervalidate", - "results": [ - { - "test_loc": "tests/services/base/mp_filtervalidate.test.sas", - "sasjs_test_id": "cb8df59b-fedb-40de-9590-f1aa948756b5", - "result": [ - { - "TEST_DESCRIPTION": "Valid filter", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.results has 0 observations" - }, - { - "TEST_DESCRIPTION": "Valid filter", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.results has 0 observations" - }, - { - "TEST_DESCRIPTION": "Valid filter", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.results has 1 observations" - } - ] - } - ] - }, - { - "test_target": "mp_validatecol", - "results": [ - { - "test_loc": "tests/services/base/mp_validatecol.test.sas", - "sasjs_test_id": "307f95c2-83b4-4caa-8b2f-ba1a673d6ff4", - "result": [ - { - "TEST_DESCRIPTION": "Testing LIBDS", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.test1 has 0 observations" - }, - { - "TEST_DESCRIPTION": "Test2 - ISNUM", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "MP_ASSERTDSOBS: Dataset work.test2 has 4 observations" - } - ] - } - ] - }, - { - "test_target": "mv_createwebservice", - "results": [ - { - "test_loc": "tests/services/viya/mv_createwebservice.test.sas", - "sasjs_test_id": "1eb31d67-d56e-4d7b-aa14-3f2d21fa3545", - "result": [ - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "FAIL", - "TEST_COMMENTS": "" - }, - { - "TEST_DESCRIPTION": "Creating web service with invisible character", - "TEST_RESULT": "PASS", - "TEST_COMMENTS": "" - } - ] - } - ] - } - ] -} \ No newline at end of file From f2942f20325a8e78568ab6a4d08cacc4d6792d2c Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Wed, 5 May 2021 01:39:53 +0300 Subject: [PATCH 59/70] chore: adding sasjsresults to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9411085..b911be2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules .DS_Store sasjsbuild/ +sasjsresults/ # avoid filenames with spaces being committed to source control **\ ** From 1b5ad93cad99fd21c6126c0c9253b699549522ce Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Wed, 5 May 2021 11:48:38 +0300 Subject: [PATCH 60/70] chore: updating all.sas --- all.sas | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/all.sas b/all.sas index 2d7f5ce..5ab16db 100644 --- a/all.sas +++ b/all.sas @@ -6249,6 +6249,71 @@ alter table &libds modify &var char(&len); %mp_createconstraints(inds=&dsconst,outds=&dsconst._addd,execute=YES) +%mend; +/** + @file + @brief Used to validate variables in a dataset + @details Useful when sanitising inputs, to ensure that they arrive with a + certain pattern. + Usage: + + data test; + infile datalines4 dsd; + input; + libds=_infile_; + %mp_validatecol(libds,LIBDS,is_libds) + datalines4; + some.libname + !lib.blah + %abort + definite.ok + not.ok! + nineletrs._ + ;;;; + run; + + @param [in] incol The column to be validated + @param [in] rule The rule to apply. Current rules: + @li LIBDS - matches LIBREF.DATASET format + @param [out] outcol The variable to create, with the results of the match + +

SAS Macros

+ @li mf_getuniquename.sas + + @version 9.3 +**/ + +%macro mp_validatecol(incol,rule,outcol); + +/* tempcol is given a unique name with every invocation */ +%local tempcol; +%let tempcol=%mf_getuniquename(); + +%if &rule=ISNUM %then %do; + /* + credit SØREN LASSEN + https://sasmacro.blogspot.com/2009/06/welcome-isnum-macro.html + */ + &tempcol=input(&incol,?? best32.); + if missing(&tempcol) then &outcol=0; + else &outcol=1; + drop &tempcol; +%end; +%else %if &rule=LIBDS %then %do; + /* match libref.dataset */ + if _n_=1 then do; + retain &tempcol; + &tempcol=prxparse('/^[_a-z]\w{0,7}\.[_a-z]\w{0,31}$/i'); + if missing(&tempcol) then do; + putlog "%str(ERR)OR: Invalid expression for LIBDS"; + stop; + end; + drop &tempcol; + end; + if prxmatch(&tempcol, trim(&incol)) then &outcol=1; + else &outcol=0; +%end; + %mend; /** @file From b4834f9b4041285223c65a71e0e4c62d2fa4da0f Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Wed, 5 May 2021 20:12:06 +0300 Subject: [PATCH 61/70] fix: updates following test runs in Studio --- all.sas | 159 +++++++++++++++++++++++++++++++++++----- base/mp_assertcols.sas | 1 + base/mp_filtercheck.sas | 22 +++--- base/mp_streamfile.sas | 24 ++++-- 4 files changed, 172 insertions(+), 34 deletions(-) diff --git a/all.sas b/all.sas index 5ab16db..95f3118 100644 --- a/all.sas +++ b/all.sas @@ -1808,6 +1808,7 @@ Usage:

SAS Macros

@li mf_existds.sas @li mf_existvarlist.sas + @li mf_getvarlist.sas @li mf_wordsinstr1butnotstr2.sas @li mp_abort.sas @@ -3334,18 +3335,18 @@ data &outds; run; +data _null_; + set &outds; + call symputx('REASON_CD',reason_cd,'l'); + stop; +run; + +%mp_abort(iftrue=(&abort=YES) + mac=&sysmacroname, + msg=%str(Filter issues in &inds, first was &reason_cd, details in &outds) +) + %if %mf_nobs(&outds)>0 %then %do; - %if &abort=YES %then %do; - data _null_; - set &outds; - call symputx('REASON_CD',reason_cd,'l'); - stop; - run; - %mp_abort( - mac=&sysmacroname, - msg=%str(Filter issues in &inds, first was &reason_cd, details in &outds) - ) - %end; %let syscc=1008; %return; %end; @@ -5787,8 +5788,20 @@ proc sql %let contentype=%upcase(&contenttype); %local platform; %let platform=%mf_getplatform(); + +/** + * check engine type to avoid the below err message: + * > Function is only valid for filerefs using the CACHE access method. + */ +%local streamweb; +%let streamweb=0; +data _null_; + set sashelp.vextfl(where=(upcase(fileref)="_WEBOUT")); + if xengine='STREAM' then call symputx('streamweb',1,'l'); +run; + %if &contentype=ZIP %then %do; - %if &platform=SASMETA %then %do; + %if &platform=SASMETA and &streamweb=1 %then %do; data _null_; rc=stpsrv_header('Content-type','application/zip'); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); @@ -5802,7 +5815,7 @@ proc sql %end; %else %if &contentype=EXCEL %then %do; /* suitable for XLS format */ - %if &platform=SASMETA %then %do; + %if &platform=SASMETA and &streamweb=1 %then %do; data _null_; rc=stpsrv_header('Content-type','application/vnd.ms-excel'); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); @@ -5815,7 +5828,7 @@ proc sql %end; %end; %else %if &contentype=XLSX %then %do; - %if &platform=SASMETA %then %do; + %if &platform=SASMETA and &streamweb=1 %then %do; data _null_; rc=stpsrv_header('Content-type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); @@ -5830,7 +5843,7 @@ proc sql %end; %end; %else %if &contentype=TEXT %then %do; - %if &platform=SASMETA %then %do; + %if &platform=SASMETA and &streamweb=1 %then %do; data _null_; rc=stpsrv_header('Content-type','application/text'); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); @@ -5843,7 +5856,7 @@ proc sql %end; %end; %else %if &contentype=CSV %then %do; - %if &platform=SASMETA %then %do; + %if &platform=SASMETA and &streamweb=1 %then %do; data _null_; rc=stpsrv_header('Content-type','application/csv'); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); @@ -5873,7 +5886,8 @@ proc sql %mp_binarycopy(inloc="&inloc",outref=_webout) %end; -%mend;/** +%mend; +/** @file @brief Runs arbitrary code for a specified amount of time @details Executes a series of procs and data steps to enable performance @@ -5964,6 +5978,117 @@ quit; libname &lib clear; +%mend;/** + @file mp_testservice.sas + @brief Will execute a test against a SASjs web service on SAS 9 or Viya + @details Prepares the input files and retrieves the resulting datasets from + the response JSON. + + %mp_testjob( + duration=60*5 + ) + + Note - the _webout fileref should NOT be assigned prior to running this macro. + + @param [in] program The _PROGRAM endpoint to test + @param [in] inputfiles= A list of space seperated fileref:filename pairs as + follows: + inputfiles=inref:filename inref2:filename2 + @param [in] debug= (log) Provide the _debug value + @param [out] outlib= (0) Output libref to contain the final tables. Set to + 0 if the service output is not in JSON format. + @param [out] outref= (0) Output fileref to create, to contain the full _webout + response. + +

SAS Macros

+ @li mf_getuniquefileref.sas + @li mf_getuniquename.sas + + @version 9.4 + @author Allan Bowe + +**/ + +%macro mp_testservice(program, + inputfiles=, + debug=log, + outlib=0, + outref=0 +)/*/STORE SOURCE*/; + +/* parse the input files */ +%local webcount i var; +%let webcount=%sysfunc(countw(&inputfiles)); +%do i=1 %to &webcount; + %let var=%scan(&inputfiles,&i,%str( )); + %local webfref&i webname&i; + %let webref&i=%scan(&var,1,%str(:)); + %let webname&i=%scan(&var,2,%str(:)); +%end; + +%local fref1 ; +%let fref1=%mf_getuniquefileref(); +filename _webout "%sysfunc(pathname(work))/%mf_getuniquename().txt"; + +%local platform; +%let platform=%mf_getplatform(); +%if &platform=SASMETA %then %do; + proc stp program="&program" + %do i=1 %to &webcount; + %if &webcount=1 %then %do; + _webin_fileref="&&webref&i" + _webin_name="&&webname&i" + %end; + %else %do; + _webin_fileref&i="&&webref&i" + _webin_name&i="&&webname&i" + %end; + %end; + _webin_file_count="&webcount" + _debug="&debug"; + outputfile=_webout; + run; + + data _null_; + infile _webout; + file &fref1; + input; + length line $10000; + if index(_infile_,'>>weboutBEGIN<<') then do; + line=tranwrd(_infile_,'>>weboutBEGIN<<',''); + put line; + end; + else if index(_infile_,'>>weboutEND<<') then do; + line=tranwrd(_infile_,'>>weboutEND<<',''); + put line; + stop; + end; + else put _infile_; + run; + data _null_; + infile &fref1; + input; + put _infile_; + run; + %if &outlib ne 0 %then %do; + libname &outlib json (&fref1); + %end; + %if &outref ne 0 %then %do; + filename &outref temp; + %mp_binarycopy(inref=_webout,outref=&outref) + %end; + filename _webout clear; + +%end; +%else %if &platform=SASVIYA %then %do; + +%end; +%else %do; + %put %str(ERR)OR: Unrecognised platform: &platform; +%end; + +filename _webout clear; + %mend;/** @file mp_testwritespeedlibrary.sas @brief Tests the write speed of a new table in a SAS library diff --git a/base/mp_assertcols.sas b/base/mp_assertcols.sas index 6298b27..8ebd3d6 100644 --- a/base/mp_assertcols.sas +++ b/base/mp_assertcols.sas @@ -25,6 +25,7 @@

SAS Macros

@li mf_existds.sas @li mf_existvarlist.sas + @li mf_getvarlist.sas @li mf_wordsinstr1butnotstr2.sas @li mp_abort.sas diff --git a/base/mp_filtercheck.sas b/base/mp_filtercheck.sas index 28d2873..f74320f 100644 --- a/base/mp_filtercheck.sas +++ b/base/mp_filtercheck.sas @@ -141,18 +141,18 @@ data &outds; run; +data _null_; + set &outds; + call symputx('REASON_CD',reason_cd,'l'); + stop; +run; + +%mp_abort(iftrue=(&abort=YES) + mac=&sysmacroname, + msg=%str(Filter issues in &inds, first was &reason_cd, details in &outds) +) + %if %mf_nobs(&outds)>0 %then %do; - %if &abort=YES %then %do; - data _null_; - set &outds; - call symputx('REASON_CD',reason_cd,'l'); - stop; - run; - %mp_abort( - mac=&sysmacroname, - msg=%str(Filter issues in &inds, first was &reason_cd, details in &outds) - ) - %end; %let syscc=1008; %return; %end; diff --git a/base/mp_streamfile.sas b/base/mp_streamfile.sas index 9484d1c..5a96897 100644 --- a/base/mp_streamfile.sas +++ b/base/mp_streamfile.sas @@ -36,8 +36,20 @@ %let contentype=%upcase(&contenttype); %local platform; %let platform=%mf_getplatform(); + +/** + * check engine type to avoid the below err message: + * > Function is only valid for filerefs using the CACHE access method. + */ +%local streamweb; +%let streamweb=0; +data _null_; + set sashelp.vextfl(where=(upcase(fileref)="_WEBOUT")); + if xengine='STREAM' then call symputx('streamweb',1,'l'); +run; + %if &contentype=ZIP %then %do; - %if &platform=SASMETA %then %do; + %if &platform=SASMETA and &streamweb=1 %then %do; data _null_; rc=stpsrv_header('Content-type','application/zip'); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); @@ -51,7 +63,7 @@ %end; %else %if &contentype=EXCEL %then %do; /* suitable for XLS format */ - %if &platform=SASMETA %then %do; + %if &platform=SASMETA and &streamweb=1 %then %do; data _null_; rc=stpsrv_header('Content-type','application/vnd.ms-excel'); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); @@ -64,7 +76,7 @@ %end; %end; %else %if &contentype=XLSX %then %do; - %if &platform=SASMETA %then %do; + %if &platform=SASMETA and &streamweb=1 %then %do; data _null_; rc=stpsrv_header('Content-type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); @@ -79,7 +91,7 @@ %end; %end; %else %if &contentype=TEXT %then %do; - %if &platform=SASMETA %then %do; + %if &platform=SASMETA and &streamweb=1 %then %do; data _null_; rc=stpsrv_header('Content-type','application/text'); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); @@ -92,7 +104,7 @@ %end; %end; %else %if &contentype=CSV %then %do; - %if &platform=SASMETA %then %do; + %if &platform=SASMETA and &streamweb=1 %then %do; data _null_; rc=stpsrv_header('Content-type','application/csv'); rc=stpsrv_header('Content-disposition',"attachment; filename=&outname"); @@ -122,4 +134,4 @@ %mp_binarycopy(inloc="&inloc",outref=_webout) %end; -%mend; \ No newline at end of file +%mend; From b1380983ec7fa2a0eb60ee9f92cc36588b2eaea4 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Wed, 5 May 2021 20:16:26 +0300 Subject: [PATCH 62/70] fix: missing comma --- all.sas | 2 +- base/mp_filtercheck.sas | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/all.sas b/all.sas index 95f3118..c4f58a1 100644 --- a/all.sas +++ b/all.sas @@ -3341,7 +3341,7 @@ data _null_; stop; run; -%mp_abort(iftrue=(&abort=YES) +%mp_abort(iftrue=(&abort=YES), mac=&sysmacroname, msg=%str(Filter issues in &inds, first was &reason_cd, details in &outds) ) diff --git a/base/mp_filtercheck.sas b/base/mp_filtercheck.sas index f74320f..00748ae 100644 --- a/base/mp_filtercheck.sas +++ b/base/mp_filtercheck.sas @@ -147,7 +147,7 @@ data _null_; stop; run; -%mp_abort(iftrue=(&abort=YES) +%mp_abort(iftrue=(&abort=YES), mac=&sysmacroname, msg=%str(Filter issues in &inds, first was &reason_cd, details in &outds) ) From 04a3189a89dd7fdd4f94aa58b5972779170dc4e1 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Thu, 6 May 2021 01:07:25 +0300 Subject: [PATCH 63/70] feat: new mv_getjobresult.sas macro, corresponding test, and additional fixes --- base/mp_filtercheck.sas | 6 +- base/mp_hashdataset.sas | 2 +- base/mp_testservice.sas | 189 ++++++++++++++++++++++ tests/testinit.sas | 2 +- tests/viya/mv_createwebservice.test.sas | 16 +- tests/viya/mv_getjobresult.test.sas | 74 +++++++++ viya/mv_getjoblog.sas | 7 +- viya/mv_getjobresult.sas | 207 ++++++++++++++++++++++++ 8 files changed, 489 insertions(+), 14 deletions(-) create mode 100644 base/mp_testservice.sas create mode 100644 tests/viya/mv_getjobresult.test.sas create mode 100644 viya/mv_getjobresult.sas diff --git a/base/mp_filtercheck.sas b/base/mp_filtercheck.sas index 00748ae..96c2c79 100644 --- a/base/mp_filtercheck.sas +++ b/base/mp_filtercheck.sas @@ -70,7 +70,7 @@ * quotes, commas, periods and spaces. * Only numeric values should remain */ - +%local reason_cd; data &outds; set &inds; length reason_cd $32; @@ -147,9 +147,9 @@ data _null_; stop; run; -%mp_abort(iftrue=(&abort=YES), +%mp_abort(iftrue=(&abort=YES and %mf_nobs(&outds)>0), mac=&sysmacroname, - msg=%str(Filter issues in &inds, first was &reason_cd, details in &outds) + msg=%str(Filter issues in &inds, reason: &reason_cd, details in &outds) ) %if %mf_nobs(&outds)>0 %then %do; diff --git a/base/mp_hashdataset.sas b/base/mp_hashdataset.sas index 264cfc2..79ee366 100644 --- a/base/mp_hashdataset.sas +++ b/base/mp_hashdataset.sas @@ -56,7 +56,7 @@ retain &prevkeyvar; set &libds end=&lastvar; /* hash should include previous row */ - if _n_>1 then &keyvar=put(md5(&prevkeyvar + &keyvar=put(md5(&prevkeyvar /* loop every column, hashing every individual value */ %do i=1 %to %sysfunc(countw(&varlist)); %let var=%scan(&varlist,&i,%str( )); diff --git a/base/mp_testservice.sas b/base/mp_testservice.sas new file mode 100644 index 0000000..4876db9 --- /dev/null +++ b/base/mp_testservice.sas @@ -0,0 +1,189 @@ +/** + @file mp_testservice.sas + @brief Will execute a test against a SASjs web service on SAS 9 or Viya + @details Prepares the input files and retrieves the resulting datasets from + the response JSON. + + %mp_testjob( + duration=60*5 + ) + + Note - the _webout fileref should NOT be assigned prior to running this macro. + + @param [in] program The _PROGRAM endpoint to test + @param [in] inputfiles=(0) A list of space seperated fileref:filename pairs as + follows: + inputfiles=inref:filename inref2:filename2 + @param [in] inputparams=(0) A dataset containing name/value pairs in the + following format: + |name:$32|value:$1000| + |---|---| + |stpmacname|some value| + |mustbevalidname|can be anything, oops, %abort!!| + + @param [in] debug= (log) Provide the _debug value + @param [out] outlib= (0) Output libref to contain the final tables. Set to + 0 if the service output is not in JSON format. + @param [out] outref= (0) Output fileref to create, to contain the full _webout + response. + +

SAS Macros

+ @li mf_getplatform.sas + @li mf_getuniquefileref.sas + @li mf_getuniquename.sas + @li mp_abort.sas + @li mp_binarycopy.sas + @li mv_getjobresult.sas + @li mv_jobflow.sas + + @version 9.4 + @author Allan Bowe + +**/ + +%macro mp_testservice(program, + inputfiles=0, + inputparams=0, + debug=log, + outlib=0, + outref=0 +)/*/STORE SOURCE*/; + +/* sanitise inputparams */ +%local pcnt; +%let pcnt=0; +%if &inputparams ne 0 %then %do; + data _null_; + set &inputparams; + if not nvalid(name,'v7') then putlog (_all_)(=); + else if name in ( + 'program','inputfiles','inputparams','debug','outlib','outref' + ) then putlog (_all_)(=); + else do; + x+1; + call symputx(name,quote(cats(value)),'l'); + call symputx('pval'!!left(x),name,'l'); + call symputx('pcnt',x,'l'); + end; + run; + %mp_abort(iftrue= (%mf_nobs(&inputparams) ne &pcnt) + ,mac=&sysmacroname + ,msg=%str(Invalid values in &inputparams) + ) +%end; + +/* parse the input files */ +%local webcount i var; +%if %quote(&inputfiles) ne 0 %then %do; + %let webcount=%sysfunc(countw(&inputfiles)); + %put &=webcount; + %do i=1 %to &webcount; + %let var=%scan(&inputfiles,&i,%str( )); + %local webfref&i webname&i; + %let webref&i=%scan(&var,1,%str(:)); + %let webname&i=%scan(&var,2,%str(:)); + %put webref&i=&&webref&i; + %put webname&i=&&webname&i; + %end; +%end; +%else %let webcount=0; + + +%local fref1 webref; +%let fref1=%mf_getuniquefileref(); +%let webref=%mf_getuniquefileref(); + +%local platform; +%let platform=%mf_getplatform(); +%if &platform=SASMETA %then %do; + proc stp program="&program"; + inputparam _program="&program" + %do i=1 %to &webcount; + %if &webcount=1 %then %do; + _webin_fileref="&&webref&i" + _webin_name="&&webname&i" + %end; + %else %do; + _webin_fileref&i="&&webref&i" + _webin_name&i="&&webname&i" + %end; + %end; + _webin_file_count="&webcount" + _debug="&debug" + %do i=1 %to &pcnt; + /* resolve name only, proc stp fetches value */ + &&pval&i=&&&&&&pval&i + %end; + ; + %do i=1 %to &webcount; + inputfile &&webref&i; + %end; + outputfile _webout=&webref; + run; + + data _null_; + infile &webref; + file &fref1; + input; + length line $10000; + if index(_infile_,'>>weboutBEGIN<<') then do; + line=tranwrd(_infile_,'>>weboutBEGIN<<',''); + put line; + end; + else if index(_infile_,'>>weboutEND<<') then do; + line=tranwrd(_infile_,'>>weboutEND<<',''); + put line; + stop; + end; + else put _infile_; + run; + data _null_; + infile &fref1; + input; + put _infile_; + run; + %if &outlib ne 0 %then %do; + libname &outlib json (&fref1); + %end; + %if &outref ne 0 %then %do; + filename &outref temp; + %mp_binarycopy(inref=&webref,outref=&outref) + %end; + +%end; +%else %if &platform=SASVIYA %then %do; + data ; + _program="&program"; + run; + + %mv_jobflow(inds=&syslast + ,maxconcurrency=1 + ,outds=work.results + ,outref=&fref1 + ) + /* show the log */ + data _null_; + infile &fref1; + input; + putlog _infile_; + run; + /* get the uri to fetch results */ + data _null_; + set work.results; + call symputx('uri',uri); + run; + /* fetch results from webout.json */ + %mv_getjobresult(uri=&uri, + result=WEBOUT_JSON, + outref=&outref, + outlib=&outlib + ) + +%end; +%else %do; + %put %str(ERR)OR: Unrecognised platform: &platform; +%end; + +filename &webref clear; + +%mend; \ No newline at end of file diff --git a/tests/testinit.sas b/tests/testinit.sas index 4daa961..611a982 100644 --- a/tests/testinit.sas +++ b/tests/testinit.sas @@ -5,4 +5,4 @@ **/ /* location in metadata or SAS Drive for temporary files */ -%let mcTestAppLoc=/Public/temp/test; \ No newline at end of file +%let mcTestAppLoc=/Public/temp/macrocore; \ No newline at end of file diff --git a/tests/viya/mv_createwebservice.test.sas b/tests/viya/mv_createwebservice.test.sas index 2c8f8e7..215e76f 100644 --- a/tests/viya/mv_createwebservice.test.sas +++ b/tests/viya/mv_createwebservice.test.sas @@ -19,23 +19,27 @@ data _null_; put '01'x; run; %mv_createwebservice( - path=&mcTestAppLoc/tests/macros, + path=&mcTestAppLoc/temp/macros, code=testref, name=mv_createwebservice ) filename compare temp; %mv_getjobcode( - path=&mcTestAppLoc/tests/macros + path=&mcTestAppLoc/temp/macros ,name=mv_createwebservice ,outref=compare; ) data test_results; length test_description $256 test_result $4 test_comments $256; - infile compare; + infile compare end=eof; input; - if _infile_='01'x then test_result='PASS'; - else test_result='FAIL'; - test_description="Creating web service with invisible character"; + if eof then do; + if _infile_='01'x then test_result='PASS'; + else test_result='FAIL'; + test_description="Creating web service with invisible character"; + output; + stop; + end; run; \ No newline at end of file diff --git a/tests/viya/mv_getjobresult.test.sas b/tests/viya/mv_getjobresult.test.sas new file mode 100644 index 0000000..943b8c2 --- /dev/null +++ b/tests/viya/mv_getjobresult.test.sas @@ -0,0 +1,74 @@ +/** + @file + @brief Testing mv_createwebservice macro + +

SAS Macros

+ @li mp_assertdsobs.sas + @li mv_createwebservice.sas + @li mv_getjobresult.sas + @li mv_jobflow.sas + +**/ + +/** + * Test Case 1 + */ + +/* create a service */ +filename testref temp; +data _null_; + file testref; + put 'data test; set sashelp.class;run;'; + put '%webout(OPEN)'; + put '%webout(OBJ,test)'; + put '%webout(CLOSE)'; +run; +%mv_createwebservice( + path=&mcTestAppLoc/services/temp, + code=testref, + name=testsvc +) + +/* trigger and wait for it to finish */ +data work.inputjobs; + _program="&mcTestAppLoc/services/temp/testsvc"; +run; +%mv_jobflow(inds=work.inputjobs + ,maxconcurrency=4 + ,outds=work.results + ,outref=myjoblog +) +/* stream the log */ +data _null_; + infile myjoblog; + input; + put _infile_; +run; + +/* fetch the uri */ +data _null_; + set work.results; + call symputx('uri',uri); + put (_all_)(=); +run; + +/* now get the results */ +%mv_getjobresult(uri=&uri + ,result=WEBOUT_JSON + ,outref=myweb + ,outlib=myweblib +) +data _null_; + infile myweb; + input; + putlog _infile_; +run; +data work.out; + set myweblib.test; + put (_all_)(=); +run; +%mp_assertdsobs(work.out, + desc=Test1 - 19 obs from sashelp.class in service result, + test=EQUALS 19, + outds=work.test_results +) \ No newline at end of file diff --git a/viya/mv_getjoblog.sas b/viya/mv_getjoblog.sas index b72c553..a5cf25a 100644 --- a/viya/mv_getjoblog.sas +++ b/viya/mv_getjoblog.sas @@ -54,13 +54,14 @@ 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] 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 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). diff --git a/viya/mv_getjobresult.sas b/viya/mv_getjobresult.sas new file mode 100644 index 0000000..107e51d --- /dev/null +++ b/viya/mv_getjobresult.sas @@ -0,0 +1,207 @@ +/** + @file + @brief Extract the result from a completed SAS Viya Job + @details Extracts result from a Viya job and writes it out to a fileref + and/or a JSON-engine library. + + 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 test; + rand=ranuni(0)*1000; + do x=1 to rand; + y=rand*4; + output; + end; + run; + proc sort data=&syslast + by descending y; + run; + %webout(OPEN) + %webout(OBJ, test) + %webout(CLOSE) + ;;;; + %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 result (In this case, WEBOUT): + + %mv_getjobresult(uri=&uri,result=WEBOUT_JSON,outref=myweb,outlib=myweblib) + + + @param [in] access_token_var= The global macro variable containing 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` (unquoted). + + @param [out] result= (WEBOUT_JSON) The result type to capture. Resolves + to "_[column name]" from the results table when parsed with the JSON libname + engine. + + @param [out] outref= (0) The output fileref to which to write the results + @param [out] outlib= (0) The output library to which to assign the results + (assumes the data is in JSON format) + + + @version VIYA V.03.05 + @author Allan Bowe, source: https://github.com/sasjs/core + +

SAS Macros

+ @li mp_abort.sas + @li mp_binarycopy.sas + @li mf_getplatform.sas + @li mf_existfileref.sas + +**/ + +%macro mv_getjobresult(uri=0 + ,contextName=SAS Job Execution compute context + ,access_token_var=ACCESS_TOKEN + ,grant_type=sas_services + ,mdebug=0 + ,result=WEBOUT_JSON + ,outref=0 + ,outlib=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) +) + +%if &outref ne 0 and %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); + +/* fetch job info */ +%local fname1; +%let fname1=%mf_getuniquefileref(); +proc http method='GET' out=&fname1 &oauth_bearer + url="&base_uri&uri"; + headers "Accept"="application/json" + %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; + +/* extract results link */ +%local lib1 resuri; +%let lib1=%mf_getuniquelibref(); +libname &lib1 JSON fileref=&fname1; +data _null_; + set &lib1..results; + call symputx('resuri',_&result,'l'); + putlog (_all_)(=); +run; +%mp_abort(iftrue=("&resuri"=".") + ,mac=&sysmacroname + ,msg=%str(Variable _&result did not exist in the response json) +) + +/* extract results */ +%local fname2; +%let fname2=%mf_getuniquefileref(); +proc http method='GET' out=&fname2 &oauth_bearer + url="&base_uri&resuri/content?limit=10000"; + headers "Accept"="application/json" + %if &grant_type=authorization_code %then %do; + "Authorization"="Bearer &&&access_token_var" + %end; + ; +run; + +%if &outref ne 0 %then %do; + filename &outref temp; + %mp_binarycopy(inref=&fname2,outref=&outref) +%end; +%if &outlib ne 0 %then %do; + libname &outlib JSON fileref=&fname2; +%end; + +%if &mdebug=0 %then %do; + filename &fname1 clear; + filename &fname2 clear; + libname &lib1 clear; +%end; +%else %do; + %put _local_; +%end; +%mend; From 61701f3c6a708550f330fa863de0984c3b9a93a6 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Thu, 6 May 2021 15:06:13 +0300 Subject: [PATCH 64/70] fix: enabling fileref as output option for sas code obtained via mm_getstpcode. Also updated some doc headers and macro footers. --- all.sas | 389 +++++++++++++++++++++++++++++++++++----- base/mf_uid.sas | 2 +- base/mp_validatecol.sas | 3 +- meta/mm_getstpcode.sas | 43 +++-- 4 files changed, 373 insertions(+), 64 deletions(-) diff --git a/all.sas b/all.sas index c4f58a1..faf96e1 100644 --- a/all.sas +++ b/all.sas @@ -1505,7 +1505,7 @@ Usage: &today._&now._&sysjobid._%sysevalf(%sysfunc(ranuni(0))*999,CEIL) -%mend;/** +%mend mf_uid;/** @file @brief Checks if a set of macro variables exist / contain values. @details Writes ERROR to log if abortType is SOFT, else will call %mf_abort. @@ -3264,7 +3264,7 @@ run; * quotes, commas, periods and spaces. * Only numeric values should remain */ - +%local reason_cd; data &outds; set &inds; length reason_cd $32; @@ -3341,9 +3341,9 @@ data _null_; stop; run; -%mp_abort(iftrue=(&abort=YES), +%mp_abort(iftrue=(&abort=YES and %mf_nobs(&outds)>0), mac=&sysmacroname, - msg=%str(Filter issues in &inds, first was &reason_cd, details in &outds) + msg=%str(Filter issues in &inds, reason: &reason_cd, details in &outds) ) %if %mf_nobs(&outds)>0 %then %do; @@ -4775,7 +4775,7 @@ create table &outds (rename=( retain &prevkeyvar; set &libds end=&lastvar; /* hash should include previous row */ - if _n_>1 then &keyvar=put(md5(&prevkeyvar + &keyvar=put(md5(&prevkeyvar /* loop every column, hashing every individual value */ %do i=1 %to %sysfunc(countw(&varlist)); %let var=%scan(&varlist,&i,%str( )); @@ -5991,9 +5991,16 @@ libname &lib clear; Note - the _webout fileref should NOT be assigned prior to running this macro. @param [in] program The _PROGRAM endpoint to test - @param [in] inputfiles= A list of space seperated fileref:filename pairs as - follows: - inputfiles=inref:filename inref2:filename2 + @param [in] inputfiles=(0) A list of space seperated fileref:filename pairs as + follows: + inputfiles=inref:filename inref2:filename2 + @param [in] inputparams=(0) A dataset containing name/value pairs in the + following format: + |name:$32|value:$1000| + |---|---| + |stpmacname|some value| + |mustbevalidname|can be anything, oops, %abort!!| + @param [in] debug= (log) Provide the _debug value @param [out] outlib= (0) Output libref to contain the final tables. Set to 0 if the service output is not in JSON format. @@ -6001,8 +6008,13 @@ libname &lib clear; response.

SAS Macros

+ @li mf_getplatform.sas @li mf_getuniquefileref.sas @li mf_getuniquename.sas + @li mp_abort.sas + @li mp_binarycopy.sas + @li mv_getjobresult.sas + @li mv_jobflow.sas @version 9.4 @author Allan Bowe @@ -6010,30 +6022,62 @@ libname &lib clear; **/ %macro mp_testservice(program, - inputfiles=, + inputfiles=0, + inputparams=0, debug=log, outlib=0, outref=0 )/*/STORE SOURCE*/; -/* parse the input files */ -%local webcount i var; -%let webcount=%sysfunc(countw(&inputfiles)); -%do i=1 %to &webcount; - %let var=%scan(&inputfiles,&i,%str( )); - %local webfref&i webname&i; - %let webref&i=%scan(&var,1,%str(:)); - %let webname&i=%scan(&var,2,%str(:)); +/* sanitise inputparams */ +%local pcnt; +%let pcnt=0; +%if &inputparams ne 0 %then %do; + data _null_; + set &inputparams; + if not nvalid(name,'v7') then putlog (_all_)(=); + else if name in ( + 'program','inputfiles','inputparams','debug','outlib','outref' + ) then putlog (_all_)(=); + else do; + x+1; + call symputx(name,quote(cats(value)),'l'); + call symputx('pval'!!left(x),name,'l'); + call symputx('pcnt',x,'l'); + end; + run; + %mp_abort(iftrue= (%mf_nobs(&inputparams) ne &pcnt) + ,mac=&sysmacroname + ,msg=%str(Invalid values in &inputparams) + ) %end; -%local fref1 ; +/* parse the input files */ +%local webcount i var; +%if %quote(&inputfiles) ne 0 %then %do; + %let webcount=%sysfunc(countw(&inputfiles)); + %put &=webcount; + %do i=1 %to &webcount; + %let var=%scan(&inputfiles,&i,%str( )); + %local webfref&i webname&i; + %let webref&i=%scan(&var,1,%str(:)); + %let webname&i=%scan(&var,2,%str(:)); + %put webref&i=&&webref&i; + %put webname&i=&&webname&i; + %end; +%end; +%else %let webcount=0; + + +%local fref1 webref; %let fref1=%mf_getuniquefileref(); -filename _webout "%sysfunc(pathname(work))/%mf_getuniquename().txt"; +%let webref=%mf_getuniquefileref(); %local platform; %let platform=%mf_getplatform(); %if &platform=SASMETA %then %do; - proc stp program="&program" + proc stp program="&program"; + inputparam _program="&program" %do i=1 %to &webcount; %if &webcount=1 %then %do; _webin_fileref="&&webref&i" @@ -6045,12 +6089,20 @@ filename _webout "%sysfunc(pathname(work))/%mf_getuniquename().txt"; %end; %end; _webin_file_count="&webcount" - _debug="&debug"; - outputfile=_webout; + _debug="&debug" + %do i=1 %to &pcnt; + /* resolve name only, proc stp fetches value */ + &&pval&i=&&&&&&pval&i + %end; + ; + %do i=1 %to &webcount; + inputfile &&webref&i; + %end; + outputfile _webout=&webref; run; data _null_; - infile _webout; + infile &webref; file &fref1; input; length line $10000; @@ -6075,19 +6127,44 @@ filename _webout "%sysfunc(pathname(work))/%mf_getuniquename().txt"; %end; %if &outref ne 0 %then %do; filename &outref temp; - %mp_binarycopy(inref=_webout,outref=&outref) + %mp_binarycopy(inref=&webref,outref=&outref) %end; - filename _webout clear; %end; %else %if &platform=SASVIYA %then %do; + data ; + _program="&program"; + run; + + %mv_jobflow(inds=&syslast + ,maxconcurrency=1 + ,outds=work.results + ,outref=&fref1 + ) + /* show the log */ + data _null_; + infile &fref1; + input; + putlog _infile_; + run; + /* get the uri to fetch results */ + data _null_; + set work.results; + call symputx('uri',uri); + run; + /* fetch results from webout.json */ + %mv_getjobresult(uri=&uri, + result=WEBOUT_JSON, + outref=&outref, + outlib=&outlib + ) %end; %else %do; %put %str(ERR)OR: Unrecognised platform: &platform; %end; -filename _webout clear; +filename &webref clear; %mend;/** @file mp_testwritespeedlibrary.sas @@ -6399,6 +6476,7 @@ alter table &libds modify &var char(&len); @param [in] incol The column to be validated @param [in] rule The rule to apply. Current rules: + @li ISNUM - checks if the variable is numeric @li LIBDS - matches LIBREF.DATASET format @param [out] outcol The variable to create, with the results of the match @@ -6439,7 +6517,7 @@ alter table &libds modify &var char(&len); else &outcol=0; %end; -%mend; +%mend mp_validatecol; /** @file @brief Creates a zip file @@ -10481,21 +10559,24 @@ filename __mc2 clear; %mend;/** @file - @brief Writes the code of an to an external file, or the log if none provided - @details Get the + @brief Writes the code of an STP to an external file + @details Fetches the SAS code from a Stored Process where the code is stored + in metadata. - usage: + Usage: %mm_getstpcode(tree=/some/meta/path ,name=someSTP ,outloc=/some/unquoted/filename.ext ) - @param tree= The metadata path of the Stored Process (can also contain name) - @param name= Stored Process name. Leave blank if included above. - @param outloc= full and unquoted path to the desired text file. This will be - overwritten if it already exists. If not provided, the code will be written - to the log. + @param [in] tree= The metadata path of the Stored Process (can also contain + name) + @param [in] name= Stored Process name. Leave blank if included above. + @param [out] outloc= (0) full and unquoted path to the desired text file. + This will be overwritten if it already exists. + @param [out] outref= (0) Fileref to which to write the code. + @param [out] showlog=(NO) Set to YES to print log to the window @author Allan Bowe @@ -10504,8 +10585,10 @@ filename __mc2 clear; %macro mm_getstpcode( tree=/User Folders/sasdemo/somestp ,name= - ,outloc= + ,outloc=0 + ,outref=0 ,mDebug=1 + ,showlog=NO ); %local mD; @@ -10573,14 +10656,18 @@ data _null_; stop; %local outeng; -%if %length(&outloc)=0 %then %let outeng=TEMP; +%if "&outloc"="0" %then %let outeng=TEMP; %else %let outeng="&outloc"; +%local fref; +%if &outref=0 %then %let fref=%mf_getuniquefileref(); +%else %let fref=&outref; + /* read the content, byte by byte, resolving escaped chars */ -filename __outdoc &outeng lrecl=100000; +filename &fref &outeng lrecl=100000; data _null_; length filein 8 fileid 8; filein = fopen("__getdoc","I",1,"B"); - fileid = fopen("__outdoc","O",1,"B"); + fileid = fopen("&fref","O",1,"B"); rec = "20"x; length entity $6; do while(fread(filein)=0); @@ -10621,9 +10708,9 @@ data _null_; rc=fclose(fileid); run; -%if &outeng=TEMP %then %do; +%if &showlog=YES %then %do; data _null_; - infile __outdoc lrecl=32767 end=last; + infile &fref lrecl=32767 end=last; input; if _n_=1 then putlog '>>stpcodeBEGIN<<'; putlog _infile_; @@ -10632,9 +10719,11 @@ run; %end; filename __getdoc clear; -filename __outdoc clear; +%if &outref=0 %then %do; + filename &fref clear; +%end; -%mend; +%mend mm_getstpcode; /** @file @brief Returns a dataset with all Stored Processes, or just those in a @@ -14550,13 +14639,14 @@ filename &fname3 clear; 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] 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 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). @@ -14762,6 +14852,213 @@ run; +/** + @file + @brief Extract the result from a completed SAS Viya Job + @details Extracts result from a Viya job and writes it out to a fileref + and/or a JSON-engine library. + + 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 test; + rand=ranuni(0)*1000; + do x=1 to rand; + y=rand*4; + output; + end; + run; + proc sort data=&syslast + by descending y; + run; + %webout(OPEN) + %webout(OBJ, test) + %webout(CLOSE) + ;;;; + %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 result (In this case, WEBOUT): + + %mv_getjobresult(uri=&uri,result=WEBOUT_JSON,outref=myweb,outlib=myweblib) + + + @param [in] access_token_var= The global macro variable containing 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` (unquoted). + + @param [out] result= (WEBOUT_JSON) The result type to capture. Resolves + to "_[column name]" from the results table when parsed with the JSON libname + engine. + + @param [out] outref= (0) The output fileref to which to write the results + @param [out] outlib= (0) The output library to which to assign the results + (assumes the data is in JSON format) + + + @version VIYA V.03.05 + @author Allan Bowe, source: https://github.com/sasjs/core + +

SAS Macros

+ @li mp_abort.sas + @li mp_binarycopy.sas + @li mf_getplatform.sas + @li mf_existfileref.sas + +**/ + +%macro mv_getjobresult(uri=0 + ,contextName=SAS Job Execution compute context + ,access_token_var=ACCESS_TOKEN + ,grant_type=sas_services + ,mdebug=0 + ,result=WEBOUT_JSON + ,outref=0 + ,outlib=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) +) + +%if &outref ne 0 and %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); + +/* fetch job info */ +%local fname1; +%let fname1=%mf_getuniquefileref(); +proc http method='GET' out=&fname1 &oauth_bearer + url="&base_uri&uri"; + headers "Accept"="application/json" + %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; + +/* extract results link */ +%local lib1 resuri; +%let lib1=%mf_getuniquelibref(); +libname &lib1 JSON fileref=&fname1; +data _null_; + set &lib1..results; + call symputx('resuri',_&result,'l'); + putlog (_all_)(=); +run; +%mp_abort(iftrue=("&resuri"=".") + ,mac=&sysmacroname + ,msg=%str(Variable _&result did not exist in the response json) +) + +/* extract results */ +%local fname2; +%let fname2=%mf_getuniquefileref(); +proc http method='GET' out=&fname2 &oauth_bearer + url="&base_uri&resuri/content?limit=10000"; + headers "Accept"="application/json" + %if &grant_type=authorization_code %then %do; + "Authorization"="Bearer &&&access_token_var" + %end; + ; +run; + +%if &outref ne 0 %then %do; + filename &outref temp; + %mp_binarycopy(inref=&fname2,outref=&outref) +%end; +%if &outlib ne 0 %then %do; + libname &outlib JSON fileref=&fname2; +%end; + +%if &mdebug=0 %then %do; + filename &fname1 clear; + filename &fname2 clear; + libname &lib1 clear; +%end; +%else %do; + %put _local_; +%end; +%mend; /** @file @brief Extract the status from a running SAS Viya job diff --git a/base/mf_uid.sas b/base/mf_uid.sas index 01eee7f..ab4310a 100644 --- a/base/mf_uid.sas +++ b/base/mf_uid.sas @@ -18,4 +18,4 @@ &today._&now._&sysjobid._%sysevalf(%sysfunc(ranuni(0))*999,CEIL) -%mend; \ No newline at end of file +%mend mf_uid; \ No newline at end of file diff --git a/base/mp_validatecol.sas b/base/mp_validatecol.sas index 147919a..f544853 100644 --- a/base/mp_validatecol.sas +++ b/base/mp_validatecol.sas @@ -22,6 +22,7 @@ @param [in] incol The column to be validated @param [in] rule The rule to apply. Current rules: + @li ISNUM - checks if the variable is numeric @li LIBDS - matches LIBREF.DATASET format @param [out] outcol The variable to create, with the results of the match @@ -62,4 +63,4 @@ else &outcol=0; %end; -%mend; +%mend mp_validatecol; diff --git a/meta/mm_getstpcode.sas b/meta/mm_getstpcode.sas index 7de5ac0..0856f60 100644 --- a/meta/mm_getstpcode.sas +++ b/meta/mm_getstpcode.sas @@ -1,20 +1,23 @@ /** @file - @brief Writes the code of an to an external file, or the log if none provided - @details Get the + @brief Writes the code of an STP to an external file + @details Fetches the SAS code from a Stored Process where the code is stored + in metadata. - usage: + Usage: %mm_getstpcode(tree=/some/meta/path ,name=someSTP ,outloc=/some/unquoted/filename.ext ) - @param tree= The metadata path of the Stored Process (can also contain name) - @param name= Stored Process name. Leave blank if included above. - @param outloc= full and unquoted path to the desired text file. This will be - overwritten if it already exists. If not provided, the code will be written - to the log. + @param [in] tree= The metadata path of the Stored Process (can also contain + name) + @param [in] name= Stored Process name. Leave blank if included above. + @param [out] outloc= (0) full and unquoted path to the desired text file. + This will be overwritten if it already exists. + @param [out] outref= (0) Fileref to which to write the code. + @param [out] showlog=(NO) Set to YES to print log to the window @author Allan Bowe @@ -23,8 +26,10 @@ %macro mm_getstpcode( tree=/User Folders/sasdemo/somestp ,name= - ,outloc= + ,outloc=0 + ,outref=0 ,mDebug=1 + ,showlog=NO ); %local mD; @@ -92,14 +97,18 @@ data _null_; stop; %local outeng; -%if %length(&outloc)=0 %then %let outeng=TEMP; +%if "&outloc"="0" %then %let outeng=TEMP; %else %let outeng="&outloc"; +%local fref; +%if &outref=0 %then %let fref=%mf_getuniquefileref(); +%else %let fref=&outref; + /* read the content, byte by byte, resolving escaped chars */ -filename __outdoc &outeng lrecl=100000; +filename &fref &outeng lrecl=100000; data _null_; length filein 8 fileid 8; filein = fopen("__getdoc","I",1,"B"); - fileid = fopen("__outdoc","O",1,"B"); + fileid = fopen("&fref","O",1,"B"); rec = "20"x; length entity $6; do while(fread(filein)=0); @@ -140,9 +149,9 @@ data _null_; rc=fclose(fileid); run; -%if &outeng=TEMP %then %do; +%if &showlog=YES %then %do; data _null_; - infile __outdoc lrecl=32767 end=last; + infile &fref lrecl=32767 end=last; input; if _n_=1 then putlog '>>stpcodeBEGIN<<'; putlog _infile_; @@ -151,6 +160,8 @@ run; %end; filename __getdoc clear; -filename __outdoc clear; +%if &outref=0 %then %do; + filename &fref clear; +%end; -%mend; +%mend mm_getstpcode; From b61b5f1856e5d7011a43c00e0ec5ce6fda4e7ea9 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Thu, 6 May 2021 19:05:14 +0300 Subject: [PATCH 65/70] fix: adding dependency --- all.sas | 3 +++ meta/mm_getstpcode.sas | 3 +++ 2 files changed, 6 insertions(+) diff --git a/all.sas b/all.sas index faf96e1..7244ae4 100644 --- a/all.sas +++ b/all.sas @@ -10578,6 +10578,9 @@ filename __mc2 clear; @param [out] outref= (0) Fileref to which to write the code. @param [out] showlog=(NO) Set to YES to print log to the window +

SAS Macros

+ @li mf_getuniquefileref.sas + @author Allan Bowe **/ diff --git a/meta/mm_getstpcode.sas b/meta/mm_getstpcode.sas index 0856f60..ac471af 100644 --- a/meta/mm_getstpcode.sas +++ b/meta/mm_getstpcode.sas @@ -19,6 +19,9 @@ @param [out] outref= (0) Fileref to which to write the code. @param [out] showlog=(NO) Set to YES to print log to the window +

SAS Macros

+ @li mf_getuniquefileref.sas + @author Allan Bowe **/ From b9d33b38bf25091ea3ef364bff0eeb91b0f5815d Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Thu, 6 May 2021 20:58:38 +0300 Subject: [PATCH 66/70] feat: new (generic) mp_assert macro, and new feature (type filter) for mf_getvarlist. Added/updated tests for mp_filtercheck and mp_validatecol and mf_getvarlist. --- all.sas | 106 +++++++++++++++++++++++++---- base/mf_getvarlist.sas | 30 ++++---- base/mf_getvartype.sas | 2 +- base/mp_assert.sas | 56 +++++++++++++++ base/mp_filtercheck.sas | 19 +++++- tests/base/mf_getvarlist.test.sas | 60 ++++++++++++++++ tests/base/mp_filtercheck.test.sas | 21 ++++++ tests/base/mp_validatecol.test.sas | 2 +- 8 files changed, 267 insertions(+), 29 deletions(-) create mode 100644 base/mp_assert.sas create mode 100644 tests/base/mf_getvarlist.test.sas diff --git a/all.sas b/all.sas index 7244ae4..23cc361 100644 --- a/all.sas +++ b/all.sas @@ -1051,6 +1051,10 @@ options noquotelenmax; @param [in] dlm= ( ) Provide a delimiter (eg comma or space) to separate the variables @param [in] quote= (none) use either DOUBLE or SINGLE to quote the results + @param [in] typefilter= (A) Filter for certain types of column. Valid values: + @li A Return All columns + @li C Return Character columns + @li N Return Numeric columns @version 9.2 @author Allan Bowe @@ -1060,9 +1064,10 @@ options noquotelenmax; %macro mf_getvarlist(libds ,dlm=%str( ) ,quote=no + ,typefilter=A )/*/STORE SOURCE*/; /* declare local vars */ - %local outvar dsid nvars x rc dlm q var; + %local outvar dsid nvars x rc dlm q var vtype; /* credit Rowland Hale - byte34 is double quote, 39 is single quote */ %if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34)); @@ -1070,21 +1075,22 @@ options noquotelenmax; /* open dataset in macro */ %let dsid=%sysfunc(open(&libds)); - %if &dsid %then %do; %let nvars=%sysfunc(attrn(&dsid,NVARS)); %if &nvars>0 %then %do; - /* add first dataset variable to global macro variable */ - %let outvar=&q.%sysfunc(varname(&dsid,1))&q.; - /* add remaining variables with supplied delimeter */ + /* add variables with supplied delimeter */ %do x=1 %to &nvars; - %let var=&q.%sysfunc(varname(&dsid,&x))&q.; - %if &var=&q&q %then %do; - %put &sysmacroname: Empty column found in &libds!; - %let var=&q. &q.; + /* get variable type */ + %let vtype=%sysfunc(vartype(&dsid,&x)); + %if &vtype=&typefilter or &typefilter=A %then %do; + %let var=&q.%sysfunc(varname(&dsid,&x))&q.; + %if &var=&q&q %then %do; + %put &sysmacroname: Empty column found in &libds!; + %let var=&q. &q.; + %end; + %if %quote(&outvar)=%quote() %then %let outvar=&var; + %else %let outvar=&outvar.&dlm.&var.; %end; - %if &x=1 %then %let outvar=&var; - %else %let outvar=&outvar.&dlm.&var.; %end; %end; %let rc=%sysfunc(close(&dsid)); @@ -1094,7 +1100,7 @@ options noquotelenmax; %let rc=%sysfunc(close(&dsid)); %end; &outvar -%mend;/** +%mend mf_getvarlist;/** @file @brief Returns the position of a variable in dataset (varnum attribute). @details Uses varnum function to determine position. @@ -1194,7 +1200,7 @@ Usage: %let rc = %sysfunc(close(&dsid)); /* Return variable type */ &vtype -%mend;/** +%mend mf_getvartype;/** @file @brief Returns the engine type of a SAS fileref @details Queries sashelp.vextfl to get the xengine value. @@ -1782,6 +1788,61 @@ Usage: %mend; /** @endcond *//** + @file + @brief Generic assertion + @details Useful in the context of writing sasjs tests. The results of the + test are _appended_ to the &outds. table. + + Example usage: + + %mp_assert(iftrue=(1=1), + desc=Obviously true + ) + + %mp_assert(iftrue=(1=0), + desc=Will fail + ) + + @param [in] iftrue= (1=1) A condition where, if true, the test is a PASS. + Else, the test is a fail. + + @param [in] desc= (Testing observations) The user provided test description + @param [out] outds= (work.test_results) The output dataset to contain the + results. If it does not exist, it will be created, with the following format: + |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| + |---|---|---| + |User Provided description|PASS|Column &inds contained ALL columns| + + @version 9.2 + @author Allan Bowe + +**/ + +%macro mp_assert(iftrue=(1=1), + desc=0, + outds=work.test_results +)/*/STORE SOURCE*/; + + data ; + length test_description $256 test_result $4 test_comments $256; + test_description=symget('desc'); + test_comments="&sysmacroname: Test result of "!!symget('iftrue'); + %if %eval(%unquote(&iftrue)) %then %do; + test_result='PASS'; + %end; + %else %do; + test_result='FAIL'; + %end; + run; + + %local ds ; + %let ds=&syslast; + proc append base=&outds data=&ds; + run; + proc sql; + drop table &ds; + +%mend mp_assert;/** @file @brief Asserts the existence (or not) of columns @details Useful in the context of writing sasjs tests. The results of the @@ -3238,6 +3299,7 @@ run; @li mp_abort.sas @li mf_getuniquefileref.sas @li mf_getvarlist.sas + @li mf_getvartype.sas @li mf_nobs.sas @li mp_filtergenerate.sas @li mp_filtervalidate.sas @@ -3259,6 +3321,20 @@ run; ,msg=%str(syscc=&syscc - on macro entry) ) +/* Validate input column */ +%local vtype; +%let vtype=%mf_getvartype(&inds,RAW_VALUE); +%mp_abort(iftrue=(&abort=YES and &vtype ne C), + mac=&sysmacroname, + msg=%str(%str(ERR)OR: RAW_VALUE must be character) +) +%if &vtype ne C %then %do; + %put &sysmacroname: RAW_VALUE must be character; + %let syscc=42; + %return; +%end; + + /** * Sanitise the values based on valid value lists, then strip out * quotes, commas, periods and spaces. @@ -3266,6 +3342,8 @@ run; */ %local reason_cd; data &outds; + /*length GROUP_LOGIC SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32 + OPERATOR_NM $10 RAW_VALUE $4000;*/ set &inds; length reason_cd $32; @@ -3362,7 +3440,7 @@ run; /* this macro will also set syscc to 1008 if any issues found */ %mp_filtervalidate(&fref1,&targetds,outds=&outds,abort=&abort) -%mend; +%mend mp_filtercheck; /** @file @brief Generates a filter clause from an input table, to a fileref diff --git a/base/mf_getvarlist.sas b/base/mf_getvarlist.sas index cad5bf3..7f0ba74 100755 --- a/base/mf_getvarlist.sas +++ b/base/mf_getvarlist.sas @@ -21,6 +21,10 @@ @param [in] dlm= ( ) Provide a delimiter (eg comma or space) to separate the variables @param [in] quote= (none) use either DOUBLE or SINGLE to quote the results + @param [in] typefilter= (A) Filter for certain types of column. Valid values: + @li A Return All columns + @li C Return Character columns + @li N Return Numeric columns @version 9.2 @author Allan Bowe @@ -30,9 +34,10 @@ %macro mf_getvarlist(libds ,dlm=%str( ) ,quote=no + ,typefilter=A )/*/STORE SOURCE*/; /* declare local vars */ - %local outvar dsid nvars x rc dlm q var; + %local outvar dsid nvars x rc dlm q var vtype; /* credit Rowland Hale - byte34 is double quote, 39 is single quote */ %if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34)); @@ -40,21 +45,22 @@ /* open dataset in macro */ %let dsid=%sysfunc(open(&libds)); - %if &dsid %then %do; %let nvars=%sysfunc(attrn(&dsid,NVARS)); %if &nvars>0 %then %do; - /* add first dataset variable to global macro variable */ - %let outvar=&q.%sysfunc(varname(&dsid,1))&q.; - /* add remaining variables with supplied delimeter */ + /* add variables with supplied delimeter */ %do x=1 %to &nvars; - %let var=&q.%sysfunc(varname(&dsid,&x))&q.; - %if &var=&q&q %then %do; - %put &sysmacroname: Empty column found in &libds!; - %let var=&q. &q.; + /* get variable type */ + %let vtype=%sysfunc(vartype(&dsid,&x)); + %if &vtype=&typefilter or &typefilter=A %then %do; + %let var=&q.%sysfunc(varname(&dsid,&x))&q.; + %if &var=&q&q %then %do; + %put &sysmacroname: Empty column found in &libds!; + %let var=&q. &q.; + %end; + %if %quote(&outvar)=%quote() %then %let outvar=&var; + %else %let outvar=&outvar.&dlm.&var.; %end; - %if &x=1 %then %let outvar=&var; - %else %let outvar=&outvar.&dlm.&var.; %end; %end; %let rc=%sysfunc(close(&dsid)); @@ -64,4 +70,4 @@ %let rc=%sysfunc(close(&dsid)); %end; &outvar -%mend; \ No newline at end of file +%mend mf_getvarlist; \ No newline at end of file diff --git a/base/mf_getvartype.sas b/base/mf_getvartype.sas index ebd2238..65f0753 100755 --- a/base/mf_getvartype.sas +++ b/base/mf_getvartype.sas @@ -45,4 +45,4 @@ Usage: %let rc = %sysfunc(close(&dsid)); /* Return variable type */ &vtype -%mend; \ No newline at end of file +%mend mf_getvartype; \ No newline at end of file diff --git a/base/mp_assert.sas b/base/mp_assert.sas new file mode 100644 index 0000000..627931d --- /dev/null +++ b/base/mp_assert.sas @@ -0,0 +1,56 @@ +/** + @file + @brief Generic assertion + @details Useful in the context of writing sasjs tests. The results of the + test are _appended_ to the &outds. table. + + Example usage: + + %mp_assert(iftrue=(1=1), + desc=Obviously true + ) + + %mp_assert(iftrue=(1=0), + desc=Will fail + ) + + @param [in] iftrue= (1=1) A condition where, if true, the test is a PASS. + Else, the test is a fail. + + @param [in] desc= (Testing observations) The user provided test description + @param [out] outds= (work.test_results) The output dataset to contain the + results. If it does not exist, it will be created, with the following format: + |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| + |---|---|---| + |User Provided description|PASS|Column &inds contained ALL columns| + + @version 9.2 + @author Allan Bowe + +**/ + +%macro mp_assert(iftrue=(1=1), + desc=0, + outds=work.test_results +)/*/STORE SOURCE*/; + + data ; + length test_description $256 test_result $4 test_comments $256; + test_description=symget('desc'); + test_comments="&sysmacroname: Test result of "!!symget('iftrue'); + %if %eval(%unquote(&iftrue)) %then %do; + test_result='PASS'; + %end; + %else %do; + test_result='FAIL'; + %end; + run; + + %local ds ; + %let ds=&syslast; + proc append base=&outds data=&ds; + run; + proc sql; + drop table &ds; + +%mend mp_assert; \ No newline at end of file diff --git a/base/mp_filtercheck.sas b/base/mp_filtercheck.sas index 96c2c79..08806ed 100644 --- a/base/mp_filtercheck.sas +++ b/base/mp_filtercheck.sas @@ -44,6 +44,7 @@ @li mp_abort.sas @li mf_getuniquefileref.sas @li mf_getvarlist.sas + @li mf_getvartype.sas @li mf_nobs.sas @li mp_filtergenerate.sas @li mp_filtervalidate.sas @@ -65,6 +66,20 @@ ,msg=%str(syscc=&syscc - on macro entry) ) +/* Validate input column */ +%local vtype; +%let vtype=%mf_getvartype(&inds,RAW_VALUE); +%mp_abort(iftrue=(&abort=YES and &vtype ne C), + mac=&sysmacroname, + msg=%str(%str(ERR)OR: RAW_VALUE must be character) +) +%if &vtype ne C %then %do; + %put &sysmacroname: RAW_VALUE must be character; + %let syscc=42; + %return; +%end; + + /** * Sanitise the values based on valid value lists, then strip out * quotes, commas, periods and spaces. @@ -72,6 +87,8 @@ */ %local reason_cd; data &outds; + /*length GROUP_LOGIC SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32 + OPERATOR_NM $10 RAW_VALUE $4000;*/ set &inds; length reason_cd $32; @@ -168,4 +185,4 @@ run; /* this macro will also set syscc to 1008 if any issues found */ %mp_filtervalidate(&fref1,&targetds,outds=&outds,abort=&abort) -%mend; +%mend mp_filtercheck; diff --git a/tests/base/mf_getvarlist.test.sas b/tests/base/mf_getvarlist.test.sas new file mode 100644 index 0000000..6982a99 --- /dev/null +++ b/tests/base/mf_getvarlist.test.sas @@ -0,0 +1,60 @@ +/** + @file + @brief Testing mf_getvarlist macro + +

SAS Macros

+ @li mf_getvarlist.sas + +**/ + + +%let test1=%mf_getvarlist(sashelp.class); +%let test2=%mf_getvarlist(sashelp.class,dlm=X); +%let test3=%mf_getvarlist(sashelp.class,dlm=%str(,),quote=double); +%let test4=%mf_getvarlist(sashelp.class,typefilter=N); +%let test5=%mf_getvarlist(sashelp.class,typefilter=C); + +data work.test_results; + length test_description $256 test_result $4 test_comments base result $256; + test_description="Basic test"; + base=symget('test1'); + result='Name Sex Age Height Weight'; + if base=result then test_result='PASS'; + else test_result='FAIL'; + test_comments="Comparing "!!trim(base)!!' vs '!!trim(result); + output; + + test_description="DLM test"; + base=symget('test2'); + result='NameXSexXAgeXHeightXWeight'; + if base=result then test_result='PASS'; + else test_result='FAIL'; + test_comments="Comparing "!!trim(base)!!' vs '!!trim(result); + output; + + test_description="DLM + quote test"; + base=symget('test3'); + result='"Name","Sex","Age","Height","Weight"'; + if base=result then test_result='PASS'; + else test_result='FAIL'; + test_comments="Comparing "!!trim(base)!!' vs '!!trim(result); + output; + + test_description="Numeric Filter"; + base=symget('test4'); + result='Age Height Weight'; + if base=result then test_result='PASS'; + else test_result='FAIL'; + test_comments="Comparing "!!trim(base)!!' vs '!!trim(result); + output; + + test_description="Char Filter"; + base=symget('test5'); + result='Name Sex'; + if base=result then test_result='PASS'; + else test_result='FAIL'; + test_comments="Comparing "!!trim(base)!!' vs '!!trim(result); + output; + + drop base result; +run; \ No newline at end of file diff --git a/tests/base/mp_filtercheck.test.sas b/tests/base/mp_filtercheck.test.sas index 45048a7..d9acc96 100644 --- a/tests/base/mp_filtercheck.test.sas +++ b/tests/base/mp_filtercheck.test.sas @@ -5,6 +5,7 @@

SAS Macros

@li mp_filtercheck.sas @li mp_assertdsobs.sas + @li mp_assert.sas **/ @@ -125,3 +126,23 @@ run; outds=work.test_results ) +/* Supply variables with incorrect types */ +data work.inds; + infile datalines4 dsd; + input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32. + OPERATOR_NM:$10. RAW_VALUE:8; +datalines4; +AND,AND,1,age,=,0 +;;;; +run; +%let syscc=0; +%mp_filtercheck(work.inds, + targetds=sashelp.class, + outds=work.badrecords, + abort=NO +) +%mp_assert(iftrue=(&syscc=42), + desc=Throw error if RAW_VALUE is incorrect, + outds=work.test_results +) +%let syscc=0; diff --git a/tests/base/mp_validatecol.test.sas b/tests/base/mp_validatecol.test.sas index b093d47..7a37916 100644 --- a/tests/base/mp_validatecol.test.sas +++ b/tests/base/mp_validatecol.test.sas @@ -17,7 +17,7 @@ data test1; input; libds=_infile_; %mp_validatecol(libds,LIBDS,is_libds) - if libds=1; + if is_libds=1; datalines4; some.libname !lib.blah From 298acc4e50a70be7c4964f3ff11fb6c96fb216a1 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Fri, 7 May 2021 11:20:01 +0300 Subject: [PATCH 67/70] fix: setting default to best. over 8. for mf_getformat with force option --- all.sas | 10 +++++----- base/mf_getvarformat.sas | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/all.sas b/all.sas index 23cc361..e25f330 100644 --- a/all.sas +++ b/all.sas @@ -932,9 +932,9 @@ options noquotelenmax; 8. NOTE: Variable renegade does not exist in test - @param libds Two part dataset (or view) reference. - @param var Variable name for which a format should be returned - @param force Set to 1 to supply a default if the variable has no format + @param [in] libds Two part dataset (or view) reference. + @param [in] var Variable name for which a format should be returned + @param [in] force=(0) Set to 1 to supply a default if the variable has no format @returns outputs format @author Allan Bowe @@ -969,7 +969,7 @@ options noquotelenmax; %let vlen = %sysfunc(varlen(&dsid, &vnum)); %let vtype = %sysfunc(vartype(&dsid, &vnum.)); %if &vtype=C %then %let vformat=$&vlen..; - %else %let vformat=8.; + %else %let vformat=best.; %end; @@ -977,7 +977,7 @@ options noquotelenmax; %let rc = %sysfunc(close(&dsid)); /* Return variable format */ &vformat -%mend;/** +%mend mf_getVarFormat;/** @file @brief Returns the length of a variable @details Uses varlen function to identify the length of a particular variable. diff --git a/base/mf_getvarformat.sas b/base/mf_getvarformat.sas index 63aacdd..677b305 100755 --- a/base/mf_getvarformat.sas +++ b/base/mf_getvarformat.sas @@ -23,9 +23,9 @@ 8. NOTE: Variable renegade does not exist in test - @param libds Two part dataset (or view) reference. - @param var Variable name for which a format should be returned - @param force Set to 1 to supply a default if the variable has no format + @param [in] libds Two part dataset (or view) reference. + @param [in] var Variable name for which a format should be returned + @param [in] force=(0) Set to 1 to supply a default if the variable has no format @returns outputs format @author Allan Bowe @@ -60,7 +60,7 @@ %let vlen = %sysfunc(varlen(&dsid, &vnum)); %let vtype = %sysfunc(vartype(&dsid, &vnum.)); %if &vtype=C %then %let vformat=$&vlen..; - %else %let vformat=8.; + %else %let vformat=best.; %end; @@ -68,4 +68,4 @@ %let rc = %sysfunc(close(&dsid)); /* Return variable format */ &vformat -%mend; \ No newline at end of file +%mend mf_getVarFormat; \ No newline at end of file From 4e564b54097690565b0b4afcc1380fe69843d6cf Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Sat, 8 May 2021 23:27:55 +0300 Subject: [PATCH 68/70] fix: refreshed the testing toolkit, added debug options, updated documentation, included one new test (mv_getjobcode.sas) --- all.sas | 418 +++++++++++++++++------- base/mp_testservice.sas | 118 +++++-- sasjs/sasjsconfig.json | 36 +- tests/viya/mv_createwebservice.test.sas | 18 +- tests/viya/mv_getjobcode.test.sas | 49 +++ viya/mv_createwebservice.sas | 86 +++-- viya/mv_getjobcode.sas | 57 +++- viya/mv_getjoblog.sas | 10 +- viya/mv_getjobresult.sas | 31 +- viya/mv_jobexecute.sas | 48 ++- viya/mv_jobflow.sas | 20 +- viya/mv_jobwaitfor.sas | 48 ++- 12 files changed, 691 insertions(+), 248 deletions(-) create mode 100644 tests/viya/mv_getjobcode.test.sas diff --git a/all.sas b/all.sas index e25f330..cae10a2 100644 --- a/all.sas +++ b/all.sas @@ -6080,6 +6080,8 @@ libname &lib clear; |mustbevalidname|can be anything, oops, %abort!!| @param [in] debug= (log) Provide the _debug value + @param [in] viyaresult=(WEBOUT_JSON) The Viya result type to return. For + more info, see mv_getjobresult.sas @param [out] outlib= (0) Output libref to contain the final tables. Set to 0 if the service output is not in JSON format. @param [out] outref= (0) Output fileref to create, to contain the full _webout @@ -6104,8 +6106,16 @@ libname &lib clear; inputparams=0, debug=log, outlib=0, - outref=0 + outref=0, + viyaresult=WEBOUT_JSON )/*/STORE SOURCE*/; +%local mdebug; +%if &debug ne 0 %then %do; + %let mdebug=1; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let mdebug=0; /* sanitise inputparams */ %local pcnt; @@ -6130,22 +6140,6 @@ libname &lib clear; ) %end; -/* parse the input files */ -%local webcount i var; -%if %quote(&inputfiles) ne 0 %then %do; - %let webcount=%sysfunc(countw(&inputfiles)); - %put &=webcount; - %do i=1 %to &webcount; - %let var=%scan(&inputfiles,&i,%str( )); - %local webfref&i webname&i; - %let webref&i=%scan(&var,1,%str(:)); - %let webname&i=%scan(&var,2,%str(:)); - %put webref&i=&&webref&i; - %put webname&i=&&webname&i; - %end; -%end; -%else %let webcount=0; - %local fref1 webref; %let fref1=%mf_getuniquefileref(); @@ -6154,6 +6148,23 @@ libname &lib clear; %local platform; %let platform=%mf_getplatform(); %if &platform=SASMETA %then %do; + + /* parse the input files */ + %local webcount i var; + %if %quote(&inputfiles) ne 0 %then %do; + %let webcount=%sysfunc(countw(&inputfiles)); + %put &=webcount; + %do i=1 %to &webcount; + %let var=%scan(&inputfiles,&i,%str( )); + %local webfref&i webname&i; + %let webref&i=%scan(&var,1,%str(:)); + %let webname&i=%scan(&var,2,%str(:)); + %put webref&i=&&webref&i; + %put webname&i=&&webname&i; + %end; + %end; + %else %let webcount=0; + proc stp program="&program"; inputparam _program="&program" %do i=1 %to &webcount; @@ -6210,14 +6221,65 @@ libname &lib clear; %end; %else %if &platform=SASVIYA %then %do; - data ; - _program="&program"; + + /* prepare inputparams */ + %local ds1; + %let ds1=%mf_getuniquename(); + %if "&inputparams" ne "0" %then %do; + proc transpose data=&inputparams out=&ds1; + id name; + var value; + run; + %end; + %else %do; + data &ds1;run; + %end; + + /* parse the input files - convert to sasjs params */ + %local webcount i var sasjs_tables; + %if %quote(&inputfiles) ne 0 %then %do; + %let webcount=%sysfunc(countw(&inputfiles)); + %put &=webcount; + %do i=1 %to &webcount; + %let var=%scan(&inputfiles,&i,%str( )); + %local webfref&i webname&i sasjs&i.data; + %let webref&i=%scan(&var,1,%str(:)); + %let webname&i=%scan(&var,2,%str(:)); + %put webref&i=&&webref&i; + %put webname&i=&&webname&i; + + %let sasjs_tables=&sasjs_tables &&webname&i; + data _null_; + infile &&webref&i lrecl=32767; + input; + if _n_=1 then call symputx("sasjs&i.data",_infile_); + else call symputx( + "sasjs&i.data",cats(symget("sasjs&i.data"),'0D0A'x,_infile_) + ); + putlog "&sysmacroname infile: " _infile_; + run; + data &ds1; + set &ds1; + length sasjs&i.data $32767 sasjs_tables $1000; + sasjs&i.data=symget("sasjs&i.data"); + sasjs_tables=symget("sasjs_tables"); + run; + %end; + %end; + %else %let webcount=0; + + data &ds1; + retain _program "&program"; + set &ds1; + putlog "&sysmacroname inputparams:"; + putlog (_all_)(=); run; - %mv_jobflow(inds=&syslast + %mv_jobflow(inds=&ds1 ,maxconcurrency=1 ,outds=work.results ,outref=&fref1 + ,mdebug=&mdebug ) /* show the log */ data _null_; @@ -6229,12 +6291,14 @@ libname &lib clear; data _null_; set work.results; call symputx('uri',uri); + putlog "&sysmacroname: fetching results for " uri; run; /* fetch results from webout.json */ %mv_getjobresult(uri=&uri, - result=WEBOUT_JSON, + result=&viyaresult, outref=&outref, - outlib=&outlib + outlib=&outlib, + mdebug=&mdebug ) %end; @@ -6242,9 +6306,15 @@ libname &lib clear; %put %str(ERR)OR: Unrecognised platform: &platform; %end; -filename &webref clear; +%if &mdebug=0 %then %do; + filename &webref clear; +%end; +%else %do; + %put &sysmacroname exit vars:; + %put _local_; +%end; -%mend;/** +%mend mp_testservice;/** @file mp_testwritespeedlibrary.sas @brief Tests the write speed of a new table in a SAS library @details Will create a new table of a certain size in an @@ -12985,23 +13055,26 @@ run; @li mf_isblank.sas @li mv_deletejes.sas - @param path= The full path (on SAS Drive) where the service will be created - @param name= The name of the service - @param desc= The description of the service - @param precode= Space separated list of filerefs, pointing to the code that - needs to be attached to the beginning of the service - @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" + @param [in] path= The full path (on SAS Drive) where the service will be + created + @param [in] name= The name of the service + @param [in] desc= The description of the service + @param [in] precode= Space separated list of filerefs, pointing to the code + that needs to be attached to the beginning of the service + @param [in] code= Fileref(s) of the actual code to be added + @param [in] access_token_var= The global macro variable to contain the access + token + @param [in] grant_type= valid values are "password" or "authorization_code" (unquoted). The default is authorization_code. - @param replace= select NO to avoid replacing any existing service in that - location - @param adapter= the macro uses the sasjs adapter by default. To use another - adapter, add a (different) fileref here. - @param contextname= Choose a specific context on which to run the Job. Leave + @param [in] replace=(YES) Select NO to avoid replacing any existing service in + that location + @param [in] adapter= the macro uses the sasjs adapter by default. To use + another adapter, add a (different) fileref here. + @param [in] 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 + @param [in] mdebug=(0) set to 1 to enable DEBUG messages @version VIYA V.03.04 @author Allan Bowe, source: https://github.com/sasjs/core @@ -13017,9 +13090,17 @@ https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5p ,grant_type=sas_services ,replace=YES ,adapter=sasjs - ,debug=0 + ,mdebug=0 ,contextname= + ,debug=0 /* @TODO - Deprecate */ ); +%local dbg; +%if &mdebug=1 %then %do; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let dbg=*; + %local oauth_bearer; %if &grant_type=detect %then %do; %if %symexist(&access_token_var) %then %let grant_type=authorization_code; @@ -13072,7 +13153,7 @@ proc http method='GET' out=&fname1 &oauth_bearer headers "Authorization"="Bearer &&&access_token_var"; %end; run; -%if &debug %then %do; +%if &mdebug=1 %then %do; data _null_; infile &fname1; input; @@ -13111,7 +13192,7 @@ proc http method='GET' %end; 'Accept'='application/vnd.sas.collection+json' 'Accept-Language'='string'; -%if &debug=1 %then %do; +%if &mdebug=1 %then %do; debug level = 3; %end; run; @@ -13166,9 +13247,9 @@ run; * These put statements are auto generated - to change the macro, change the * source (mv_webout) and run `build.py` */ -filename sasjs temp lrecl=3000; +filename &adapter temp lrecl=3000; data _null_; - file sasjs; + file &adapter; put "/* Created on %sysfunc(datetime(),datetime19.) by &sysuserid */"; /* WEBOUT BEGIN */ put ' '; @@ -13507,11 +13588,12 @@ data _null_; run; /* insert the code, escaping double quotes and carriage returns */ +%&dbg.put &sysmacroname: Creating final input file; %local x fref freflist; %let freflist= &adapter &precode &code ; %do x=1 %to %sysfunc(countw(&freflist)); %let fref=%scan(&freflist,&x); - %put &sysmacroname: adding &fref; + %&dbg.put &sysmacroname: adding &fref fileref; data _null_; length filein 8 fileid 8; filein = fopen("&fref","I",1,"B"); @@ -13563,7 +13645,12 @@ data _null_; put '"}'; run; -/* now we can create the job!! */ +%if &mdebug=1 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then %do; + %put &sysmacroname: input about to be POSTed; + data _null_;infile &fname3;input;putlog _infile_;run; +%end; + +%&dbg.put &sysmacroname: Creating the actual service!; %local fname4; %let fname4=%mf_getuniquefileref(); proc http method='POST' @@ -13576,22 +13663,18 @@ proc http method='POST' "Authorization"="Bearer &&&access_token_var" %end; "Accept"="application/vnd.sas.job.definition+json"; -%if &debug=1 %then %do; +%if &mdebug=1 %then %do; debug level = 3; %end; run; -/*data _null_;infile &fname4;input;putlog _infile_;run;*/ +%if &mdebug=1 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then %do; + %put &sysmacroname: output from POSTing job definition; + data _null_;infile &fname4;input;putlog _infile_;run; +%end; %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; -filename &adapter clear; -libname &libref1 clear; /* get the url so we can give a helpful log message */ %local url; @@ -13606,6 +13689,19 @@ data _null_; call symputx('url',url); run; +%if &mdebug=1 %then %do; + %put &sysmacroname exit vars:; + %put _local_; +%end; +%else %do; + /* clear refs */ + filename &fname1 clear; + filename &fname2 clear; + filename &fname3 clear; + filename &fname4 clear; + filename &adapter clear; + libname &libref1 clear; +%end; %put &sysmacroname: Job &name successfully created in &path; %put &sysmacroname:; @@ -13615,7 +13711,7 @@ run; %put &sysmacroname:; %put &sysmacroname:; -%mend; +%mend mv_createwebservice; /** @file mv_deletefoldermember.sas @brief Deletes an item in a Viya folder @@ -14526,17 +14622,20 @@ libname &libref1 clear; ,outfile=/tmp/some_job.sas ) - @param [in] access_token_var= The global macro variable to contain the access token + @param [in] access_token_var= The global macro variable to contain the access + token @param [in] grant_type= valid values: - * password - * authorization_code - * detect - will check if access_token exists, if not will use sas_services if - a SASStudioV session else authorization_code. Default option. - * sas_services - will use oauth_bearer=sas_services + @li password + @liauthorization_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] path= The SAS Drive path of the job @param [in] name= The name of the job - @param [out] outref= A fileref to which to write the source code - @param [out] outfile= A file to which to write the source code + @param [in] mdebug=(0) set to 1 to enable DEBUG messages + @param [out] outref=(0) A fileref to which to write the source code (will be + created with a TEMP engine) + @param [out] outfile=(0) A file to which to write the source code @version VIYA V.03.04 @author Allan Bowe, source: https://github.com/sasjs/core @@ -14555,7 +14654,15 @@ libname &libref1 clear; ,contextName=SAS Job Execution compute context ,access_token_var=ACCESS_TOKEN ,grant_type=sas_services + ,mdebug=0 ); +%local dbg; +%if &mdebug=1 %then %do; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let dbg=*; + %local oauth_bearer; %if &grant_type=detect %then %do; %if %symexist(&access_token_var) %then %let grant_type=authorization_code; @@ -14649,21 +14756,33 @@ data _null_; run; %inc "&fpath3..lua"; /* export to desired destination */ -data _null_; - %if &outref=0 %then %do; +%if "&outref"="0" %then %do; + data _null_; file "&outfile" lrecl=32767; - %end; - %else %do; +%end; +%else %do; + filename &outref temp; + data _null_; file &outref; - %end; +%end; infile &fname2; input; put _infile_; + &dbg. putlog _infile_; run; -filename &fname1 clear; -filename &fname2 clear; -filename &fname3 clear; -%mend; + +%if &mdebug=1 %then %do; + %put &sysmacroname exit vars:; + %put _local_; +%end; +%else %do; + /* clear refs */ + filename &fname1 clear; + filename &fname2 clear; + filename &fname3 clear; +%end; + +%mend mv_getjobcode; /** @file @brief Extract the log from a completed SAS Viya Job @@ -14752,6 +14871,13 @@ filename &fname3 clear; ,grant_type=sas_services ,mdebug=0 ); +%local dbg; +%if &mdebug=1 %then %do; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let dbg=*; + %local oauth_bearer; %if &grant_type=detect %then %do; %if %symexist(&access_token_var) %then %let grant_type=authorization_code; @@ -14927,9 +15053,10 @@ run; filename &fname3 clear; %end; %else %do; + %put &sysmacroname exit vars:; %put _local_; %end; -%mend; +%mend mv_getjoblog; @@ -15004,7 +15131,9 @@ run; @param [out] result= (WEBOUT_JSON) The result type to capture. Resolves to "_[column name]" from the results table when parsed with the JSON libname - engine. + engine. Example values: + @li WEBOUT_JSON + @li WEBOUT_TXT @param [out] outref= (0) The output fileref to which to write the results @param [out] outlib= (0) The output library to which to assign the results @@ -15031,6 +15160,13 @@ run; ,outref=0 ,outlib=0 ); +%local dbg; +%if &mdebug=1 %then %do; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let dbg=*; + %local oauth_bearer; %if &grant_type=detect %then %do; %if %symexist(&access_token_var) %then %let grant_type=authorization_code; @@ -15096,6 +15232,13 @@ run; ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) ) %end; +%if &mdebug=1 %then %do; + data _null_; + infile &fname1 lrecl=32767; + input; + putlog _infile_; + run; +%end; /* extract results link */ %local lib1 resuri; @@ -15104,7 +15247,7 @@ libname &lib1 JSON fileref=&fname1; data _null_; set &lib1..results; call symputx('resuri',_&result,'l'); - putlog (_all_)(=); + &dbg putlog "&sysmacroname results: " (_all_)(=); run; %mp_abort(iftrue=("&resuri"=".") ,mac=&sysmacroname @@ -15122,6 +15265,13 @@ proc http method='GET' out=&fname2 &oauth_bearer %end; ; run; +%if &mdebug=1 %then %do; + data _null_; + infile &fname2 lrecl=32767; + input; + putlog _infile_; + run; +%end; %if &outref ne 0 %then %do; filename &outref temp; @@ -15137,9 +15287,11 @@ run; libname &lib1 clear; %end; %else %do; + %put &sysmacroname exit vars:; %put _local_; %end; -%mend; + +%mend mv_getjobresult; /** @file @brief Extract the status from a running SAS Viya job @@ -15572,24 +15724,25 @@ libname &libref1 clear; ,paramstring=%str("macvarname":"macvarvalue","answer":42) ) - @param [in] access_token_var= The global macro variable to contain the access token + @param [in] access_token_var= The global macro variable to contain the access + token @param [in] grant_type= valid values: - - * password - * authorization_code - * detect - will check if access_token exists, if not will use sas_services if - a SASStudioV session else authorization_code. Default option. - * sas_services - will use oauth_bearer=sas_services + @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] path= The SAS Drive path to the job being executed @param [in] name= The name of the job to execute - @param [in] paramstring= A JSON fragment with name:value pairs, eg: `"name":"value"` - or "name":"value","name2":42`. This will need to be wrapped in `%str()`. + @param [in] paramstring= A JSON fragment with name:value pairs, eg: + `"name":"value"` or "name":"value","name2":42`. This will need to be + wrapped in `%str()`. @param [in] contextName= Context name with which to run the job. Default = `SAS Job Execution compute context` - - @param [out] outds= The output dataset containing links (Default=work.mv_jobexecute) + @param [in] mdebug= set to 1 to enable DEBUG messages + @param [out] outds= (work.mv_jobexecute) The output dataset containing links @version VIYA V.03.04 @@ -15611,7 +15764,15 @@ libname &libref1 clear; ,grant_type=sas_services ,paramstring=0 ,outds=work.mv_jobexecute + ,mdebug=0 ); +%local dbg; +%if &mdebug=1 %then %do; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let dbg=*; + %local oauth_bearer; %if &grant_type=detect %then %do; %if %symexist(&access_token_var) %then %let grant_type=authorization_code; @@ -15713,12 +15874,17 @@ data &outds; _program="&path/&name"; run; -/* clear refs */ -filename &fname0 clear; -filename &fname1 clear; -libname &libref; - -%mend;/** +%if &mdebug=1 %then %do; + %put &sysmacroname exit vars:; + %put _local_; +%end; +%else %do; + /* clear refs */ + filename &fname0 clear; + filename &fname1 clear; + libname &libref; +%end; +%mend mv_jobexecute;/** @file @brief Execute a series of job flows @details Very (very) simple flow manager. Jobs execute in sequential waves, @@ -15856,6 +16022,13 @@ libname &libref; ,raise_err=0 ,mdebug=0 ); +%local dbg; +%if &mdebug=1 %then %do; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let dbg=*; + %local oauth_bearer; %if &grant_type=detect %then %do; %if %symexist(&access_token_var) %then %let grant_type=authorization_code; @@ -16013,6 +16186,7 @@ data;run;%let jdswaitfor=&syslast; ,name=&jobname ,paramstring=%superq(jparams&jid) ,outds=&jdsapp + ,mdebug=&mdebug ) data &jdsapp; format jobparams $32767.; @@ -16033,8 +16207,13 @@ data;run;%let jdswaitfor=&syslast; %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) + %mv_jobwaitfor(ANY + ,inds=&jdsrunning + ,outds=&jdswaitfor + ,outref=&outref + ,raise_err=&raise_err + ,mdebug=&mdebug + ) %local done; %let done=%mf_nobs(&jdswaitfor); %if &done>0 %then %do; @@ -16066,13 +16245,14 @@ data;run;%let jdswaitfor=&syslast; %end; %if &mdebug=1 %then %do; + %put &sysmacroname exit vars:; %put _local_; %end; -%mend; +%mend mv_jobflow; /** @file - @brief Takes a dataset of running jobs and waits for ANY or ALL of them to complete + @brief Takes a table of running jobs and waits for ANY/ALL of them to complete @details Will poll `/jobs/{jobId}/state` at set intervals until ANY or ALL jobs are completed. Completion is determined by reference to the returned _state_, as per the following table: @@ -16127,13 +16307,14 @@ data;run;%let jdswaitfor=&syslast; %mv_deletejes(path=/Public/temp,name=demo) - @param [in] access_token_var= The global macro variable to contain the access token + @param [in] access_token_var= The global macro variable to contain the access + token @param [in] grant_type= valid values: - password - authorization_code - - detect - will check if access_token exists, if not will use sas_services if - a SASStudioV session else authorization_code. Default option. + - detect - will check if access_token exists, if not will use sas_services + if a SASStudioV session else authorization_code. Default option. - sas_services - will use oauth_bearer=sas_services @param [in] action=Either ALL (to wait for every job) or ANY (if one job @@ -16144,9 +16325,11 @@ data;run;%let jdswaitfor=&syslast; 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 [in] mdebug= set to 1 to enable DEBUG messages @param [out] outds= The output dataset containing the list of states by job (default=work.mv_jobexecute) - @param [out] outref= A fileref to which the spawned job logs should be appended. + @param [out] outref= A fileref to which the spawned job logs should be + appended. @version VIYA V.03.04 @author Allan Bowe, source: https://github.com/sasjs/core @@ -16169,7 +16352,15 @@ data;run;%let jdswaitfor=&syslast; ,outds=work.mv_jobwaitfor ,outref=0 ,raise_err=0 + ,mdebug=0 ); +%local dbg; +%if &mdebug=1 %then %do; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let dbg=*; + %local oauth_bearer; %if &grant_type=detect %then %do; %if %symexist(&access_token_var) %then %let grant_type=authorization_code; @@ -16227,7 +16418,8 @@ run; %let fname0=%mf_getuniquefileref(); data &outds; - format _program uri $128. state $32. stateDetails $32. timestamp datetime19. jobparams $32767.; + format _program uri $128. state $32. stateDetails $32. timestamp datetime19. + jobparams $32767.; stop; run; @@ -16240,8 +16432,8 @@ run; "Authorization"="Bearer &&&access_token_var" %end; ; run; - %if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then - %do; + %if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 + %then %do; data _null_;infile &fname0;input;putlog _infile_;run; %mp_abort(mac=&sysmacroname ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) @@ -16277,7 +16469,7 @@ run; %let joburi&i=0; /* do not re-check */ /* fetch log */ %if %str(&outref) ne 0 %then %do; - %mv_getjoblog(uri=&plainuri,outref=&outref) + %mv_getjoblog(uri=&plainuri,outref=&outref,mdebug=&mdebug) %end; %end; %else %if &status=idle or &status=pending or &status=running %then %do; @@ -16292,10 +16484,11 @@ run; %end; %if (&raise_err) %then %do; - %if (&status = canceled or &status = failed or %length(&stateDetails)>0) %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; + %put %str(ERR)OR: Job &&jobname&i. did not complete. &stateDetails; %return; %end; %end; @@ -16310,10 +16503,15 @@ run; %end; %end; -/* clear refs */ -filename &fname0 clear; - -%mend;/** +%if &mdebug=1 %then %do; + %put &sysmacroname exit vars:; + %put _local_; +%end; +%else %do; + /* clear refs */ + filename &fname0 clear; +%end; +%mend mv_jobwaitfor;/** @file mv_registerclient.sas @brief Register Client and Secret (admin task) @details When building apps on SAS Viya, an client id and secret is required. diff --git a/base/mp_testservice.sas b/base/mp_testservice.sas index 4876db9..0bb83f6 100644 --- a/base/mp_testservice.sas +++ b/base/mp_testservice.sas @@ -22,6 +22,8 @@ |mustbevalidname|can be anything, oops, %abort!!| @param [in] debug= (log) Provide the _debug value + @param [in] viyaresult=(WEBOUT_JSON) The Viya result type to return. For + more info, see mv_getjobresult.sas @param [out] outlib= (0) Output libref to contain the final tables. Set to 0 if the service output is not in JSON format. @param [out] outref= (0) Output fileref to create, to contain the full _webout @@ -46,8 +48,16 @@ inputparams=0, debug=log, outlib=0, - outref=0 + outref=0, + viyaresult=WEBOUT_JSON )/*/STORE SOURCE*/; +%local mdebug; +%if &debug ne 0 %then %do; + %let mdebug=1; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let mdebug=0; /* sanitise inputparams */ %local pcnt; @@ -72,22 +82,6 @@ ) %end; -/* parse the input files */ -%local webcount i var; -%if %quote(&inputfiles) ne 0 %then %do; - %let webcount=%sysfunc(countw(&inputfiles)); - %put &=webcount; - %do i=1 %to &webcount; - %let var=%scan(&inputfiles,&i,%str( )); - %local webfref&i webname&i; - %let webref&i=%scan(&var,1,%str(:)); - %let webname&i=%scan(&var,2,%str(:)); - %put webref&i=&&webref&i; - %put webname&i=&&webname&i; - %end; -%end; -%else %let webcount=0; - %local fref1 webref; %let fref1=%mf_getuniquefileref(); @@ -96,6 +90,23 @@ %local platform; %let platform=%mf_getplatform(); %if &platform=SASMETA %then %do; + + /* parse the input files */ + %local webcount i var; + %if %quote(&inputfiles) ne 0 %then %do; + %let webcount=%sysfunc(countw(&inputfiles)); + %put &=webcount; + %do i=1 %to &webcount; + %let var=%scan(&inputfiles,&i,%str( )); + %local webfref&i webname&i; + %let webref&i=%scan(&var,1,%str(:)); + %let webname&i=%scan(&var,2,%str(:)); + %put webref&i=&&webref&i; + %put webname&i=&&webname&i; + %end; + %end; + %else %let webcount=0; + proc stp program="&program"; inputparam _program="&program" %do i=1 %to &webcount; @@ -152,14 +163,65 @@ %end; %else %if &platform=SASVIYA %then %do; - data ; - _program="&program"; + + /* prepare inputparams */ + %local ds1; + %let ds1=%mf_getuniquename(); + %if "&inputparams" ne "0" %then %do; + proc transpose data=&inputparams out=&ds1; + id name; + var value; + run; + %end; + %else %do; + data &ds1;run; + %end; + + /* parse the input files - convert to sasjs params */ + %local webcount i var sasjs_tables; + %if %quote(&inputfiles) ne 0 %then %do; + %let webcount=%sysfunc(countw(&inputfiles)); + %put &=webcount; + %do i=1 %to &webcount; + %let var=%scan(&inputfiles,&i,%str( )); + %local webfref&i webname&i sasjs&i.data; + %let webref&i=%scan(&var,1,%str(:)); + %let webname&i=%scan(&var,2,%str(:)); + %put webref&i=&&webref&i; + %put webname&i=&&webname&i; + + %let sasjs_tables=&sasjs_tables &&webname&i; + data _null_; + infile &&webref&i lrecl=32767; + input; + if _n_=1 then call symputx("sasjs&i.data",_infile_); + else call symputx( + "sasjs&i.data",cats(symget("sasjs&i.data"),'0D0A'x,_infile_) + ); + putlog "&sysmacroname infile: " _infile_; + run; + data &ds1; + set &ds1; + length sasjs&i.data $32767 sasjs_tables $1000; + sasjs&i.data=symget("sasjs&i.data"); + sasjs_tables=symget("sasjs_tables"); + run; + %end; + %end; + %else %let webcount=0; + + data &ds1; + retain _program "&program"; + set &ds1; + putlog "&sysmacroname inputparams:"; + putlog (_all_)(=); run; - %mv_jobflow(inds=&syslast + %mv_jobflow(inds=&ds1 ,maxconcurrency=1 ,outds=work.results ,outref=&fref1 + ,mdebug=&mdebug ) /* show the log */ data _null_; @@ -171,12 +233,14 @@ data _null_; set work.results; call symputx('uri',uri); + putlog "&sysmacroname: fetching results for " uri; run; /* fetch results from webout.json */ %mv_getjobresult(uri=&uri, - result=WEBOUT_JSON, + result=&viyaresult, outref=&outref, - outlib=&outlib + outlib=&outlib, + mdebug=&mdebug ) %end; @@ -184,6 +248,12 @@ %put %str(ERR)OR: Unrecognised platform: &platform; %end; -filename &webref clear; +%if &mdebug=0 %then %do; + filename &webref clear; +%end; +%else %do; + %put &sysmacroname exit vars:; + %put _local_; +%end; -%mend; \ No newline at end of file +%mend mp_testservice; \ No newline at end of file diff --git a/sasjs/sasjsconfig.json b/sasjs/sasjsconfig.json index e3432bc..abee80c 100644 --- a/sasjs/sasjsconfig.json +++ b/sasjs/sasjsconfig.json @@ -16,24 +16,6 @@ "readMe": "../../README.md" } }, - "serviceConfig": { - "initProgram": "tests/testinit.sas", - "termProgram": "tests/testterm.sas", - "serviceFolders": [ - "tests/base", - "tests/viya" - ], - "macroVars": { - "mcTestAppLoc": "/Public/temp/macrocore" - } - }, - "testConfig": { - "initProgram": "tests/testinit.sas", - "termProgram": "tests/testterm.sas", - "macroVars": { - "mcTestAppLoc": "/Public/temp/macrocore" - } - }, "defaultTarget": "viya", "targets": [ { @@ -44,7 +26,23 @@ "deployConfig": { "deployServicePack": true }, - "contextName": "SAS Job Execution compute context" + "macroFolders": [ + "base", + "meta", + "metax", + "viya", + "lua", + "tests/base", + "tests/viya" + ], + "contextName": "SAS Job Execution compute context", + "testConfig": { + "initProgram": "tests/testinit.sas", + "termProgram": "tests/testterm.sas", + "macroVars": { + "mcTestAppLoc": "/Public/temp/macrocore" + } + } } ] } \ No newline at end of file diff --git a/tests/viya/mv_createwebservice.test.sas b/tests/viya/mv_createwebservice.test.sas index 215e76f..25b7298 100644 --- a/tests/viya/mv_createwebservice.test.sas +++ b/tests/viya/mv_createwebservice.test.sas @@ -18,20 +18,20 @@ data _null_; file testref; put '01'x; run; +%put TEST1: creating web service; %mv_createwebservice( path=&mcTestAppLoc/temp/macros, - code=testref, - name=mv_createwebservice + name=mv_createwebservice, + code=testref ) - -filename compare temp; +%put TEST1: fetching web service code; %mv_getjobcode( - path=&mcTestAppLoc/temp/macros - ,name=mv_createwebservice - ,outref=compare; + path=&mcTestAppLoc/temp/macros, + name=mv_createwebservice, + outref=compare ) - -data test_results; +%put TEST1: checking web service code; +data work.test_results; length test_description $256 test_result $4 test_comments $256; infile compare end=eof; input; diff --git a/tests/viya/mv_getjobcode.test.sas b/tests/viya/mv_getjobcode.test.sas new file mode 100644 index 0000000..7eeffd4 --- /dev/null +++ b/tests/viya/mv_getjobcode.test.sas @@ -0,0 +1,49 @@ +/** + @file + @brief Testing mv_getjobcode macro + +

SAS Macros

+ @li mp_assert.sas + @li mv_createjob.sas + @li mv_getjobcode.sas + +**/ + +/** + * Test Case 1 + */ + +/* write some code to a job */ +%let incode=%str(data test; set sashelp.class;run;); +filename testref temp; +data _null_; + file testref; + put "&incode"; +run; +%mv_createjob( + code=testref, + path=&mcTestAppLoc/services/temp, + name=some_job +) + +/* now get the code back */ +%mv_getjobcode( + path=&mcTestAppLoc/services/temp, + name=some_job, + outref=mycode +) + +%let diditexist=NO; +data work.test1; + infile mycode; + input; + putlog _infile_; + line=_infile_; + check=symget('incode'); + if _infile_=symget('incode') then call symputx('diditexist','YES'); +run; + +%mp_assert( + iftrue=(&diditexist=NO), + desc=Check if the code that was sent was successfully retrieved +) \ No newline at end of file diff --git a/viya/mv_createwebservice.sas b/viya/mv_createwebservice.sas index 7e5f712..da2ddac 100644 --- a/viya/mv_createwebservice.sas +++ b/viya/mv_createwebservice.sas @@ -39,23 +39,26 @@ @li mf_isblank.sas @li mv_deletejes.sas - @param path= The full path (on SAS Drive) where the service will be created - @param name= The name of the service - @param desc= The description of the service - @param precode= Space separated list of filerefs, pointing to the code that - needs to be attached to the beginning of the service - @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" + @param [in] path= The full path (on SAS Drive) where the service will be + created + @param [in] name= The name of the service + @param [in] desc= The description of the service + @param [in] precode= Space separated list of filerefs, pointing to the code + that needs to be attached to the beginning of the service + @param [in] code= Fileref(s) of the actual code to be added + @param [in] access_token_var= The global macro variable to contain the access + token + @param [in] grant_type= valid values are "password" or "authorization_code" (unquoted). The default is authorization_code. - @param replace= select NO to avoid replacing any existing service in that - location - @param adapter= the macro uses the sasjs adapter by default. To use another - adapter, add a (different) fileref here. - @param contextname= Choose a specific context on which to run the Job. Leave + @param [in] replace=(YES) Select NO to avoid replacing any existing service in + that location + @param [in] adapter= the macro uses the sasjs adapter by default. To use + another adapter, add a (different) fileref here. + @param [in] 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 + @param [in] mdebug=(0) set to 1 to enable DEBUG messages @version VIYA V.03.04 @author Allan Bowe, source: https://github.com/sasjs/core @@ -71,9 +74,17 @@ https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5p ,grant_type=sas_services ,replace=YES ,adapter=sasjs - ,debug=0 + ,mdebug=0 ,contextname= + ,debug=0 /* @TODO - Deprecate */ ); +%local dbg; +%if &mdebug=1 %then %do; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let dbg=*; + %local oauth_bearer; %if &grant_type=detect %then %do; %if %symexist(&access_token_var) %then %let grant_type=authorization_code; @@ -126,7 +137,7 @@ proc http method='GET' out=&fname1 &oauth_bearer headers "Authorization"="Bearer &&&access_token_var"; %end; run; -%if &debug %then %do; +%if &mdebug=1 %then %do; data _null_; infile &fname1; input; @@ -165,7 +176,7 @@ proc http method='GET' %end; 'Accept'='application/vnd.sas.collection+json' 'Accept-Language'='string'; -%if &debug=1 %then %do; +%if &mdebug=1 %then %do; debug level = 3; %end; run; @@ -220,9 +231,9 @@ run; * These put statements are auto generated - to change the macro, change the * source (mv_webout) and run `build.py` */ -filename sasjs temp lrecl=3000; +filename &adapter temp lrecl=3000; data _null_; - file sasjs; + file &adapter; put "/* Created on %sysfunc(datetime(),datetime19.) by &sysuserid */"; /* WEBOUT BEGIN */ put ' '; @@ -561,11 +572,12 @@ data _null_; run; /* insert the code, escaping double quotes and carriage returns */ +%&dbg.put &sysmacroname: Creating final input file; %local x fref freflist; %let freflist= &adapter &precode &code ; %do x=1 %to %sysfunc(countw(&freflist)); %let fref=%scan(&freflist,&x); - %put &sysmacroname: adding &fref; + %&dbg.put &sysmacroname: adding &fref fileref; data _null_; length filein 8 fileid 8; filein = fopen("&fref","I",1,"B"); @@ -617,7 +629,12 @@ data _null_; put '"}'; run; -/* now we can create the job!! */ +%if &mdebug=1 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then %do; + %put &sysmacroname: input about to be POSTed; + data _null_;infile &fname3;input;putlog _infile_;run; +%end; + +%&dbg.put &sysmacroname: Creating the actual service!; %local fname4; %let fname4=%mf_getuniquefileref(); proc http method='POST' @@ -630,22 +647,18 @@ proc http method='POST' "Authorization"="Bearer &&&access_token_var" %end; "Accept"="application/vnd.sas.job.definition+json"; -%if &debug=1 %then %do; +%if &mdebug=1 %then %do; debug level = 3; %end; run; -/*data _null_;infile &fname4;input;putlog _infile_;run;*/ +%if &mdebug=1 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then %do; + %put &sysmacroname: output from POSTing job definition; + data _null_;infile &fname4;input;putlog _infile_;run; +%end; %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; -filename &adapter clear; -libname &libref1 clear; /* get the url so we can give a helpful log message */ %local url; @@ -660,6 +673,19 @@ data _null_; call symputx('url',url); run; +%if &mdebug=1 %then %do; + %put &sysmacroname exit vars:; + %put _local_; +%end; +%else %do; + /* clear refs */ + filename &fname1 clear; + filename &fname2 clear; + filename &fname3 clear; + filename &fname4 clear; + filename &adapter clear; + libname &libref1 clear; +%end; %put &sysmacroname: Job &name successfully created in &path; %put &sysmacroname:; @@ -669,4 +695,4 @@ run; %put &sysmacroname:; %put &sysmacroname:; -%mend; +%mend mv_createwebservice; diff --git a/viya/mv_getjobcode.sas b/viya/mv_getjobcode.sas index 0c4b679..d5f0ba8 100644 --- a/viya/mv_getjobcode.sas +++ b/viya/mv_getjobcode.sas @@ -10,17 +10,20 @@ ,outfile=/tmp/some_job.sas ) - @param [in] access_token_var= The global macro variable to contain the access token + @param [in] access_token_var= The global macro variable to contain the access + token @param [in] grant_type= valid values: - * password - * authorization_code - * detect - will check if access_token exists, if not will use sas_services if - a SASStudioV session else authorization_code. Default option. - * sas_services - will use oauth_bearer=sas_services + @li password + @liauthorization_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] path= The SAS Drive path of the job @param [in] name= The name of the job - @param [out] outref= A fileref to which to write the source code - @param [out] outfile= A file to which to write the source code + @param [in] mdebug=(0) set to 1 to enable DEBUG messages + @param [out] outref=(0) A fileref to which to write the source code (will be + created with a TEMP engine) + @param [out] outfile=(0) A file to which to write the source code @version VIYA V.03.04 @author Allan Bowe, source: https://github.com/sasjs/core @@ -39,7 +42,15 @@ ,contextName=SAS Job Execution compute context ,access_token_var=ACCESS_TOKEN ,grant_type=sas_services + ,mdebug=0 ); +%local dbg; +%if &mdebug=1 %then %do; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let dbg=*; + %local oauth_bearer; %if &grant_type=detect %then %do; %if %symexist(&access_token_var) %then %let grant_type=authorization_code; @@ -133,18 +144,30 @@ data _null_; run; %inc "&fpath3..lua"; /* export to desired destination */ -data _null_; - %if &outref=0 %then %do; +%if "&outref"="0" %then %do; + data _null_; file "&outfile" lrecl=32767; - %end; - %else %do; +%end; +%else %do; + filename &outref temp; + data _null_; file &outref; - %end; +%end; infile &fname2; input; put _infile_; + &dbg. putlog _infile_; run; -filename &fname1 clear; -filename &fname2 clear; -filename &fname3 clear; -%mend; + +%if &mdebug=1 %then %do; + %put &sysmacroname exit vars:; + %put _local_; +%end; +%else %do; + /* clear refs */ + filename &fname1 clear; + filename &fname2 clear; + filename &fname3 clear; +%end; + +%mend mv_getjobcode; diff --git a/viya/mv_getjoblog.sas b/viya/mv_getjoblog.sas index a5cf25a..7c4ce0a 100644 --- a/viya/mv_getjoblog.sas +++ b/viya/mv_getjoblog.sas @@ -86,6 +86,13 @@ ,grant_type=sas_services ,mdebug=0 ); +%local dbg; +%if &mdebug=1 %then %do; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let dbg=*; + %local oauth_bearer; %if &grant_type=detect %then %do; %if %symexist(&access_token_var) %then %let grant_type=authorization_code; @@ -261,9 +268,10 @@ run; filename &fname3 clear; %end; %else %do; + %put &sysmacroname exit vars:; %put _local_; %end; -%mend; +%mend mv_getjoblog; diff --git a/viya/mv_getjobresult.sas b/viya/mv_getjobresult.sas index 107e51d..2fa2304 100644 --- a/viya/mv_getjobresult.sas +++ b/viya/mv_getjobresult.sas @@ -69,7 +69,9 @@ @param [out] result= (WEBOUT_JSON) The result type to capture. Resolves to "_[column name]" from the results table when parsed with the JSON libname - engine. + engine. Example values: + @li WEBOUT_JSON + @li WEBOUT_TXT @param [out] outref= (0) The output fileref to which to write the results @param [out] outlib= (0) The output library to which to assign the results @@ -96,6 +98,13 @@ ,outref=0 ,outlib=0 ); +%local dbg; +%if &mdebug=1 %then %do; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let dbg=*; + %local oauth_bearer; %if &grant_type=detect %then %do; %if %symexist(&access_token_var) %then %let grant_type=authorization_code; @@ -161,6 +170,13 @@ run; ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) ) %end; +%if &mdebug=1 %then %do; + data _null_; + infile &fname1 lrecl=32767; + input; + putlog _infile_; + run; +%end; /* extract results link */ %local lib1 resuri; @@ -169,7 +185,7 @@ libname &lib1 JSON fileref=&fname1; data _null_; set &lib1..results; call symputx('resuri',_&result,'l'); - putlog (_all_)(=); + &dbg putlog "&sysmacroname results: " (_all_)(=); run; %mp_abort(iftrue=("&resuri"=".") ,mac=&sysmacroname @@ -187,6 +203,13 @@ proc http method='GET' out=&fname2 &oauth_bearer %end; ; run; +%if &mdebug=1 %then %do; + data _null_; + infile &fname2 lrecl=32767; + input; + putlog _infile_; + run; +%end; %if &outref ne 0 %then %do; filename &outref temp; @@ -202,6 +225,8 @@ run; libname &lib1 clear; %end; %else %do; + %put &sysmacroname exit vars:; %put _local_; %end; -%mend; + +%mend mv_getjobresult; diff --git a/viya/mv_jobexecute.sas b/viya/mv_jobexecute.sas index b8c4a2c..3cdcff3 100644 --- a/viya/mv_jobexecute.sas +++ b/viya/mv_jobexecute.sas @@ -23,24 +23,25 @@ ,paramstring=%str("macvarname":"macvarvalue","answer":42) ) - @param [in] access_token_var= The global macro variable to contain the access token + @param [in] access_token_var= The global macro variable to contain the access + token @param [in] grant_type= valid values: - - * password - * authorization_code - * detect - will check if access_token exists, if not will use sas_services if - a SASStudioV session else authorization_code. Default option. - * sas_services - will use oauth_bearer=sas_services + @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] path= The SAS Drive path to the job being executed @param [in] name= The name of the job to execute - @param [in] paramstring= A JSON fragment with name:value pairs, eg: `"name":"value"` - or "name":"value","name2":42`. This will need to be wrapped in `%str()`. + @param [in] paramstring= A JSON fragment with name:value pairs, eg: + `"name":"value"` or "name":"value","name2":42`. This will need to be + wrapped in `%str()`. @param [in] contextName= Context name with which to run the job. Default = `SAS Job Execution compute context` - - @param [out] outds= The output dataset containing links (Default=work.mv_jobexecute) + @param [in] mdebug= set to 1 to enable DEBUG messages + @param [out] outds= (work.mv_jobexecute) The output dataset containing links @version VIYA V.03.04 @@ -62,7 +63,15 @@ ,grant_type=sas_services ,paramstring=0 ,outds=work.mv_jobexecute + ,mdebug=0 ); +%local dbg; +%if &mdebug=1 %then %do; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let dbg=*; + %local oauth_bearer; %if &grant_type=detect %then %do; %if %symexist(&access_token_var) %then %let grant_type=authorization_code; @@ -164,9 +173,14 @@ data &outds; _program="&path/&name"; run; -/* clear refs */ -filename &fname0 clear; -filename &fname1 clear; -libname &libref; - -%mend; \ No newline at end of file +%if &mdebug=1 %then %do; + %put &sysmacroname exit vars:; + %put _local_; +%end; +%else %do; + /* clear refs */ + filename &fname0 clear; + filename &fname1 clear; + libname &libref; +%end; +%mend mv_jobexecute; \ No newline at end of file diff --git a/viya/mv_jobflow.sas b/viya/mv_jobflow.sas index 5f45d6b..fab42bb 100644 --- a/viya/mv_jobflow.sas +++ b/viya/mv_jobflow.sas @@ -136,6 +136,13 @@ ,raise_err=0 ,mdebug=0 ); +%local dbg; +%if &mdebug=1 %then %do; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let dbg=*; + %local oauth_bearer; %if &grant_type=detect %then %do; %if %symexist(&access_token_var) %then %let grant_type=authorization_code; @@ -293,6 +300,7 @@ data;run;%let jdswaitfor=&syslast; ,name=&jobname ,paramstring=%superq(jparams&jid) ,outds=&jdsapp + ,mdebug=&mdebug ) data &jdsapp; format jobparams $32767.; @@ -313,8 +321,13 @@ data;run;%let jdswaitfor=&syslast; %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) + %mv_jobwaitfor(ANY + ,inds=&jdsrunning + ,outds=&jdswaitfor + ,outref=&outref + ,raise_err=&raise_err + ,mdebug=&mdebug + ) %local done; %let done=%mf_nobs(&jdswaitfor); %if &done>0 %then %do; @@ -346,7 +359,8 @@ data;run;%let jdswaitfor=&syslast; %end; %if &mdebug=1 %then %do; + %put &sysmacroname exit vars:; %put _local_; %end; -%mend; +%mend mv_jobflow; diff --git a/viya/mv_jobwaitfor.sas b/viya/mv_jobwaitfor.sas index 4735f7c..ff31f8b 100644 --- a/viya/mv_jobwaitfor.sas +++ b/viya/mv_jobwaitfor.sas @@ -1,6 +1,6 @@ /** @file - @brief Takes a dataset of running jobs and waits for ANY or ALL of them to complete + @brief Takes a table of running jobs and waits for ANY/ALL of them to complete @details Will poll `/jobs/{jobId}/state` at set intervals until ANY or ALL jobs are completed. Completion is determined by reference to the returned _state_, as per the following table: @@ -55,13 +55,14 @@ %mv_deletejes(path=/Public/temp,name=demo) - @param [in] access_token_var= The global macro variable to contain the access token + @param [in] access_token_var= The global macro variable to contain the access + token @param [in] grant_type= valid values: - password - authorization_code - - detect - will check if access_token exists, if not will use sas_services if - a SASStudioV session else authorization_code. Default option. + - detect - will check if access_token exists, if not will use sas_services + if a SASStudioV session else authorization_code. Default option. - sas_services - will use oauth_bearer=sas_services @param [in] action=Either ALL (to wait for every job) or ANY (if one job @@ -72,9 +73,11 @@ 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 [in] mdebug= set to 1 to enable DEBUG messages @param [out] outds= The output dataset containing the list of states by job (default=work.mv_jobexecute) - @param [out] outref= A fileref to which the spawned job logs should be appended. + @param [out] outref= A fileref to which the spawned job logs should be + appended. @version VIYA V.03.04 @author Allan Bowe, source: https://github.com/sasjs/core @@ -97,7 +100,15 @@ ,outds=work.mv_jobwaitfor ,outref=0 ,raise_err=0 + ,mdebug=0 ); +%local dbg; +%if &mdebug=1 %then %do; + %put &sysmacroname entry vars:; + %put _local_; +%end; +%else %let dbg=*; + %local oauth_bearer; %if &grant_type=detect %then %do; %if %symexist(&access_token_var) %then %let grant_type=authorization_code; @@ -155,7 +166,8 @@ run; %let fname0=%mf_getuniquefileref(); data &outds; - format _program uri $128. state $32. stateDetails $32. timestamp datetime19. jobparams $32767.; + format _program uri $128. state $32. stateDetails $32. timestamp datetime19. + jobparams $32767.; stop; run; @@ -168,8 +180,8 @@ run; "Authorization"="Bearer &&&access_token_var" %end; ; run; - %if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then - %do; + %if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 + %then %do; data _null_;infile &fname0;input;putlog _infile_;run; %mp_abort(mac=&sysmacroname ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE) @@ -205,7 +217,7 @@ run; %let joburi&i=0; /* do not re-check */ /* fetch log */ %if %str(&outref) ne 0 %then %do; - %mv_getjoblog(uri=&plainuri,outref=&outref) + %mv_getjoblog(uri=&plainuri,outref=&outref,mdebug=&mdebug) %end; %end; %else %if &status=idle or &status=pending or &status=running %then %do; @@ -220,10 +232,11 @@ run; %end; %if (&raise_err) %then %do; - %if (&status = canceled or &status = failed or %length(&stateDetails)>0) %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; + %put %str(ERR)OR: Job &&jobname&i. did not complete. &stateDetails; %return; %end; %end; @@ -238,7 +251,12 @@ run; %end; %end; -/* clear refs */ -filename &fname0 clear; - -%mend; \ No newline at end of file +%if &mdebug=1 %then %do; + %put &sysmacroname exit vars:; + %put _local_; +%end; +%else %do; + /* clear refs */ + filename &fname0 clear; +%end; +%mend mv_jobwaitfor; \ No newline at end of file From 68aee776d3a72a096ab4de27e3d301b4224f696c Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Mon, 10 May 2021 01:25:51 +0300 Subject: [PATCH 69/70] feat: new mp_ds2fmtds macro - converts a dataset to a new dataset where all values are the formatted values. Also added a test. --- all.sas | 98 ++++++++++++++++++++++++++++++++- base/mf_getuniquename.sas | 4 +- base/mp_ds2fmtds.sas | 95 ++++++++++++++++++++++++++++++++ tests/base/mp_ds2fmtds.test.sas | 28 ++++++++++ 4 files changed, 221 insertions(+), 4 deletions(-) create mode 100644 base/mp_ds2fmtds.sas create mode 100644 tests/base/mp_ds2fmtds.test.sas diff --git a/all.sas b/all.sas index cae10a2..3c39e4d 100644 --- a/all.sas +++ b/all.sas @@ -801,8 +801,8 @@ options noquotelenmax; %macro mf_getuniquename(prefix=MC); - &prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix)) -%mend;/** +&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix)) +%mend mf_getuniquename;/** @file @brief Returns a userid according to session context @details In a workspace session, a user is generally represented by @@ -3254,6 +3254,100 @@ run; %mend;/** + @file + @brief Converts every value in a dataset to it's formatted value + @details Converts every value to it's formatted value. All variables will + become character, and will be in the same order. + + Usage: + + %mp_ds2fmtds(sashelp.cars,work.cards) + + @param [in] libds The library.dataset to be converted + @param [out] outds The dataset to create. + + @version 9.2 + @author Allan Bowe +**/ + +%macro mp_ds2fmtds(libds, outds +)/*/STORE SOURCE*/; + +/* validations */ +%if not %sysfunc(exist(&libds)) %then %do; + %put %str(WARN)ING: &libds does not exist; + %return; +%end; +%if %index(&libds,.)=0 %then %let libds=WORK.&libds; + +/* grab metadata */ +proc contents noprint data=&libds + out=_data_(keep=name type length format formatl formatd varnum); +run; +proc sort; + by varnum; +run; + +/* prepare formats and varnames */ +data _null_; + set &syslast end=last; + name=upcase(name); + /* fix formats */ + if type=2 or type=6 then do; + length fmt $49.; + if format='' then fmt=cats('$',length,'.'); + else if formatl=0 then fmt=cats(format,'.'); + else fmt=cats(format,formatl,'.'); + newlen=max(formatl,length); + end; + else do; + if format='' then fmt='best.'; + else if formatl=0 then fmt=cats(format,'.'); + else if formatd=0 then fmt=cats(format,formatl,'.'); + else fmt=cats(format,formatl,'.',formatd); + /* needs to be wide, for datetimes etc */ + newlen=max(length,formatl,24); + end; + /* 32 char unique name */ + newname='sasjs'!!substr(cats(put(md5(name),$hex32.)),1,27); + + call symputx(cats('name',_n_),name,'l'); + call symputx(cats('newname',_n_),newname,'l'); + call symputx(cats('len',_n_),newlen,'l'); + call symputx(cats('fmt',_n_),fmt,'l'); + call symputx(cats('type',_n_),type,'l'); + if last then call symputx('nobs',_n_,'l'); +run; + +/* clean up */ +proc sql; +drop table &syslast; + +%if &nobs=0 %then %do; + %put Dataset &libds has no columns! + data &outds; + set &libds; + run; + %return; +%end; + +data &outds; + /* rename on entry */ + set &libds(rename=( +%local i; +%do i=1 %to &nobs; + &&name&i=&&newname&i +%end; + )); +%do i=1 %to &nobs; + length &&name&i $&&len&i; + &&name&i=left(put(&&newname&i,&&fmt&i)); + drop &&newname&i; +%end; + if _error_ then call symputx('syscc',1012); +run; + +%mend mp_ds2fmtds;/** @file @brief Checks an input filter table for validity @details Performs checks on the input table to ensure it arrives in the diff --git a/base/mf_getuniquename.sas b/base/mf_getuniquename.sas index 8ccaec8..97e2a72 100644 --- a/base/mf_getuniquename.sas +++ b/base/mf_getuniquename.sas @@ -18,5 +18,5 @@ %macro mf_getuniquename(prefix=MC); - &prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix)) -%mend; \ No newline at end of file +&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix)) +%mend mf_getuniquename; \ No newline at end of file diff --git a/base/mp_ds2fmtds.sas b/base/mp_ds2fmtds.sas new file mode 100644 index 0000000..5c18571 --- /dev/null +++ b/base/mp_ds2fmtds.sas @@ -0,0 +1,95 @@ +/** + @file + @brief Converts every value in a dataset to it's formatted value + @details Converts every value to it's formatted value. All variables will + become character, and will be in the same order. + + Usage: + + %mp_ds2fmtds(sashelp.cars,work.cards) + + @param [in] libds The library.dataset to be converted + @param [out] outds The dataset to create. + + @version 9.2 + @author Allan Bowe +**/ + +%macro mp_ds2fmtds(libds, outds +)/*/STORE SOURCE*/; + +/* validations */ +%if not %sysfunc(exist(&libds)) %then %do; + %put %str(WARN)ING: &libds does not exist; + %return; +%end; +%if %index(&libds,.)=0 %then %let libds=WORK.&libds; + +/* grab metadata */ +proc contents noprint data=&libds + out=_data_(keep=name type length format formatl formatd varnum); +run; +proc sort; + by varnum; +run; + +/* prepare formats and varnames */ +data _null_; + set &syslast end=last; + name=upcase(name); + /* fix formats */ + if type=2 or type=6 then do; + length fmt $49.; + if format='' then fmt=cats('$',length,'.'); + else if formatl=0 then fmt=cats(format,'.'); + else fmt=cats(format,formatl,'.'); + newlen=max(formatl,length); + end; + else do; + if format='' then fmt='best.'; + else if formatl=0 then fmt=cats(format,'.'); + else if formatd=0 then fmt=cats(format,formatl,'.'); + else fmt=cats(format,formatl,'.',formatd); + /* needs to be wide, for datetimes etc */ + newlen=max(length,formatl,24); + end; + /* 32 char unique name */ + newname='sasjs'!!substr(cats(put(md5(name),$hex32.)),1,27); + + call symputx(cats('name',_n_),name,'l'); + call symputx(cats('newname',_n_),newname,'l'); + call symputx(cats('len',_n_),newlen,'l'); + call symputx(cats('fmt',_n_),fmt,'l'); + call symputx(cats('type',_n_),type,'l'); + if last then call symputx('nobs',_n_,'l'); +run; + +/* clean up */ +proc sql; +drop table &syslast; + +%if &nobs=0 %then %do; + %put Dataset &libds has no columns! + data &outds; + set &libds; + run; + %return; +%end; + +data &outds; + /* rename on entry */ + set &libds(rename=( +%local i; +%do i=1 %to &nobs; + &&name&i=&&newname&i +%end; + )); +%do i=1 %to &nobs; + length &&name&i $&&len&i; + &&name&i=left(put(&&newname&i,&&fmt&i)); + drop &&newname&i; +%end; + if _error_ then call symputx('syscc',1012); +run; + +%mend mp_ds2fmtds; \ No newline at end of file diff --git a/tests/base/mp_ds2fmtds.test.sas b/tests/base/mp_ds2fmtds.test.sas new file mode 100644 index 0000000..0d6de69 --- /dev/null +++ b/tests/base/mp_ds2fmtds.test.sas @@ -0,0 +1,28 @@ +/** + @file + @brief Testing mp_ds2fmtds.sas macro + +

SAS Macros

+ @li mp_ds2fmtds.sas + @li mp_assert.sas + +**/ + +proc sql; +create table test as select * from dictionary.tables where libname='SASHELP'; + +filename inc temp; +data _null_; + set work.test; + file inc; + line=cats('%mp_ds2fmtds(sashelp.',memname,',',memname,')'); + put line; +run; + +%inc inc; + +%mp_assert( + iftrue=(&syscc=0), + desc=Checking tables were created successfully, + outds=work.test_results +) \ No newline at end of file From 6a2ac51925424cfc9a3059d58f48c6f4378c6023 Mon Sep 17 00:00:00 2001 From: Allan Bowe Date: Mon, 10 May 2021 03:42:53 +0300 Subject: [PATCH 70/70] fix: adding formatting to mp_jsonout, and a test --- README.md | 6 +- all.sas | 196 +++++++++++++++++++++++++++++++-- base/mp_ds2fmtds.sas | 5 +- base/mp_jsonout.sas | 65 ++++++++++- meta/mm_createwebservice.sas | 63 ++++++++++- tests/base/mp_jsonout.test.sas | 42 +++++++ viya/mv_createwebservice.sas | 63 ++++++++++- 7 files changed, 423 insertions(+), 17 deletions(-) create mode 100644 tests/base/mp_jsonout.test.sas diff --git a/README.md b/README.md index 94746be..077dbbc 100644 --- a/README.md +++ b/README.md @@ -116,19 +116,19 @@ The **Macro Core** documentation is created using [doxygen](http://www.doxygen.n All macros must be commented in the doxygen format, to enable the [online documentation](https://core.sasjs.io). ### Dependencies -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 Includes. When compiling projects using the [SASjs CLI](https://cli.sasjs.io) the doxygen header is scanned for ` @li` items under the following headers: ```sas

SAS Macros

@li mf_nobs.sas @li mm_assignlib.sas -

SAS Programs

+

SAS Includes

@li somefile.ddl SOMEFREF @li someprogram.sas FREFTWO ``` -The CLI can then extract all the dependencies and insert as precode (SAS Macros) or in a temp engine fileref (SAS Programs) when creating SAS Jobs and Services. +The CLI can then extract all the dependencies and insert as precode (SAS Macros) or in a temp engine fileref (SAS Includes) when creating SAS Jobs and Services. When contributing to this library, it is therefore important to ensure that all dependencies are listed in the header in this format. diff --git a/all.sas b/all.sas index 3c39e4d..5d7673e 100644 --- a/all.sas +++ b/all.sas @@ -3261,11 +3261,14 @@ run; Usage: - %mp_ds2fmtds(sashelp.cars,work.cards) + %mp_ds2fmtds(sashelp.cars,work.cars) @param [in] libds The library.dataset to be converted @param [out] outds The dataset to create. +

Related Macros

+ @li mp_jsonout.sas + @version 9.2 @author Allan Bowe **/ @@ -5013,6 +5016,8 @@ create table &outds (rename=( @param dbg= DEPRECATED - was used to conditionally add PRETTY to proc json but this can cause line truncation in large files. +

Related Macros

+ @li mp_ds2fmtds.sas @version 9.2 @author Allan Bowe @@ -5020,10 +5025,11 @@ create table &outds (rename=( **/ -%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=PROCJSON,dbg=0 +%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 )/*/STORE SOURCE*/; %put output location=&jref; %if &action=OPEN %then %do; + OPTIONS NOBOMFILE; data _null_;file &jref encoding='utf-8'; put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"'; run; @@ -5051,6 +5057,64 @@ create table &outds (rename=( %put &sysmacroname: &ds NOT FOUND!!!; %return; %end; + %if &fmt=Y %then %do; + %put converting every variable to a formatted variable; + /* see mp_ds2fmtds.sas for source */ + proc contents noprint data=&ds + out=_data_(keep=name type length format formatl formatd varnum); + run; + proc sort; + by varnum; + run; + %local fmtds; + %let fmtds=%scan(&syslast,2,.); + /* prepare formats and varnames */ + data _null_; + set &fmtds end=last; + name=upcase(name); + /* fix formats */ + if type=2 or type=6 then do; + length fmt $49.; + if format='' then fmt=cats('$',length,'.'); + else if formatl=0 then fmt=cats(format,'.'); + else fmt=cats(format,formatl,'.'); + newlen=max(formatl,length); + end; + else do; + if format='' then fmt='best.'; + else if formatl=0 then fmt=cats(format,'.'); + else if formatd=0 then fmt=cats(format,formatl,'.'); + else fmt=cats(format,formatl,'.',formatd); + /* needs to be wide, for datetimes etc */ + newlen=max(length,formatl,24); + end; + /* 32 char unique name */ + newname='sasjs'!!substr(cats(put(md5(name),$hex32.)),1,27); + + call symputx(cats('name',_n_),name,'l'); + call symputx(cats('newname',_n_),newname,'l'); + call symputx(cats('len',_n_),newlen,'l'); + call symputx(cats('fmt',_n_),fmt,'l'); + call symputx(cats('type',_n_),type,'l'); + if last then call symputx('nobs',_n_,'l'); + run; + data &fmtds; + /* rename on entry */ + set &ds(rename=( + %local i; + %do i=1 %to &nobs; + &&name&i=&&newname&i + %end; + )); + %do i=1 %to &nobs; + length &&name&i $&&len&i; + &&name&i=left(put(&&newname&i,&&fmt&i)); + drop &&newname&i; + %end; + if _error_ then call symputx('syscc',1012); + run; + %let ds=&fmtds; + %end; /* &fmt=Y */ data _null_;file &jref mod ; put "["; call symputx('cols',0,'l'); proc sort @@ -5134,7 +5198,7 @@ create table &outds (rename=( put "}"; run; %end; -%mend; +%mend mp_jsonout; /** @file @brief Convert all library members to CARDS files @@ -8791,10 +8855,11 @@ data _null_; put "/* Created on %sysfunc(datetime(),datetime19.) by %mf_getuser() */"; /* WEBOUT BEGIN */ put ' '; - put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=PROCJSON,dbg=0 '; + put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 '; put ')/*/STORE SOURCE*/; '; put '%put output location=&jref; '; put '%if &action=OPEN %then %do; '; + put ' OPTIONS NOBOMFILE; '; put ' data _null_;file &jref encoding=''utf-8''; '; put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; '; put ' run; '; @@ -8822,6 +8887,64 @@ data _null_; put ' %put &sysmacroname: &ds NOT FOUND!!!; '; put ' %return; '; put ' %end; '; + put ' %if &fmt=Y %then %do; '; + put ' %put converting every variable to a formatted variable; '; + put ' /* see mp_ds2fmtds.sas for source */ '; + put ' proc contents noprint data=&ds '; + put ' out=_data_(keep=name type length format formatl formatd varnum); '; + put ' run; '; + put ' proc sort; '; + put ' by varnum; '; + put ' run; '; + put ' %local fmtds; '; + put ' %let fmtds=%scan(&syslast,2,.); '; + put ' /* prepare formats and varnames */ '; + put ' data _null_; '; + put ' set &fmtds end=last; '; + put ' name=upcase(name); '; + put ' /* fix formats */ '; + put ' if type=2 or type=6 then do; '; + put ' length fmt $49.; '; + put ' if format='''' then fmt=cats(''$'',length,''.''); '; + put ' else if formatl=0 then fmt=cats(format,''.''); '; + put ' else fmt=cats(format,formatl,''.''); '; + put ' newlen=max(formatl,length); '; + put ' end; '; + put ' else do; '; + put ' if format='''' then fmt=''best.''; '; + put ' else if formatl=0 then fmt=cats(format,''.''); '; + put ' else if formatd=0 then fmt=cats(format,formatl,''.''); '; + put ' else fmt=cats(format,formatl,''.'',formatd); '; + put ' /* needs to be wide, for datetimes etc */ '; + put ' newlen=max(length,formatl,24); '; + put ' end; '; + put ' /* 32 char unique name */ '; + put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); '; + put ' '; + put ' call symputx(cats(''name'',_n_),name,''l''); '; + put ' call symputx(cats(''newname'',_n_),newname,''l''); '; + put ' call symputx(cats(''len'',_n_),newlen,''l''); '; + put ' call symputx(cats(''fmt'',_n_),fmt,''l''); '; + put ' call symputx(cats(''type'',_n_),type,''l''); '; + put ' if last then call symputx(''nobs'',_n_,''l''); '; + put ' run; '; + put ' data &fmtds; '; + put ' /* rename on entry */ '; + put ' set &ds(rename=( '; + put ' %local i; '; + put ' %do i=1 %to &nobs; '; + put ' &&name&i=&&newname&i '; + put ' %end; '; + put ' )); '; + put ' %do i=1 %to &nobs; '; + put ' length &&name&i $&&len&i; '; + put ' &&name&i=left(put(&&newname&i,&&fmt&i)); '; + put ' drop &&newname&i; '; + put ' %end; '; + put ' if _error_ then call symputx(''syscc'',1012); '; + put ' run; '; + put ' %let ds=&fmtds; '; + put ' %end; /* &fmt=Y */ '; put ' data _null_;file &jref mod ; '; put ' put "["; call symputx(''cols'',0,''l''); '; put ' proc sort '; @@ -8905,7 +9028,7 @@ data _null_; put ' put "}"; '; put ' run; '; put '%end; '; - put '%mend; '; + put '%mend mp_jsonout; '; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y); '; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug '; put ' sasjs_tables; '; @@ -13347,10 +13470,11 @@ data _null_; put "/* Created on %sysfunc(datetime(),datetime19.) by &sysuserid */"; /* WEBOUT BEGIN */ put ' '; - put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=PROCJSON,dbg=0 '; + put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 '; put ')/*/STORE SOURCE*/; '; put '%put output location=&jref; '; put '%if &action=OPEN %then %do; '; + put ' OPTIONS NOBOMFILE; '; put ' data _null_;file &jref encoding=''utf-8''; '; put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; '; put ' run; '; @@ -13378,6 +13502,64 @@ data _null_; put ' %put &sysmacroname: &ds NOT FOUND!!!; '; put ' %return; '; put ' %end; '; + put ' %if &fmt=Y %then %do; '; + put ' %put converting every variable to a formatted variable; '; + put ' /* see mp_ds2fmtds.sas for source */ '; + put ' proc contents noprint data=&ds '; + put ' out=_data_(keep=name type length format formatl formatd varnum); '; + put ' run; '; + put ' proc sort; '; + put ' by varnum; '; + put ' run; '; + put ' %local fmtds; '; + put ' %let fmtds=%scan(&syslast,2,.); '; + put ' /* prepare formats and varnames */ '; + put ' data _null_; '; + put ' set &fmtds end=last; '; + put ' name=upcase(name); '; + put ' /* fix formats */ '; + put ' if type=2 or type=6 then do; '; + put ' length fmt $49.; '; + put ' if format='''' then fmt=cats(''$'',length,''.''); '; + put ' else if formatl=0 then fmt=cats(format,''.''); '; + put ' else fmt=cats(format,formatl,''.''); '; + put ' newlen=max(formatl,length); '; + put ' end; '; + put ' else do; '; + put ' if format='''' then fmt=''best.''; '; + put ' else if formatl=0 then fmt=cats(format,''.''); '; + put ' else if formatd=0 then fmt=cats(format,formatl,''.''); '; + put ' else fmt=cats(format,formatl,''.'',formatd); '; + put ' /* needs to be wide, for datetimes etc */ '; + put ' newlen=max(length,formatl,24); '; + put ' end; '; + put ' /* 32 char unique name */ '; + put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); '; + put ' '; + put ' call symputx(cats(''name'',_n_),name,''l''); '; + put ' call symputx(cats(''newname'',_n_),newname,''l''); '; + put ' call symputx(cats(''len'',_n_),newlen,''l''); '; + put ' call symputx(cats(''fmt'',_n_),fmt,''l''); '; + put ' call symputx(cats(''type'',_n_),type,''l''); '; + put ' if last then call symputx(''nobs'',_n_,''l''); '; + put ' run; '; + put ' data &fmtds; '; + put ' /* rename on entry */ '; + put ' set &ds(rename=( '; + put ' %local i; '; + put ' %do i=1 %to &nobs; '; + put ' &&name&i=&&newname&i '; + put ' %end; '; + put ' )); '; + put ' %do i=1 %to &nobs; '; + put ' length &&name&i $&&len&i; '; + put ' &&name&i=left(put(&&newname&i,&&fmt&i)); '; + put ' drop &&newname&i; '; + put ' %end; '; + put ' if _error_ then call symputx(''syscc'',1012); '; + put ' run; '; + put ' %let ds=&fmtds; '; + put ' %end; /* &fmt=Y */ '; put ' data _null_;file &jref mod ; '; put ' put "["; call symputx(''cols'',0,''l''); '; put ' proc sort '; @@ -13461,7 +13643,7 @@ data _null_; put ' put "}"; '; put ' run; '; put '%end; '; - put '%mend; '; + put '%mend mp_jsonout; '; put '%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y); '; put '%global _webin_file_count _webin_fileuri _debug _omittextlog _webin_name '; put ' sasjs_tables SYS_JES_JOB_URI; '; diff --git a/base/mp_ds2fmtds.sas b/base/mp_ds2fmtds.sas index 5c18571..9b810a6 100644 --- a/base/mp_ds2fmtds.sas +++ b/base/mp_ds2fmtds.sas @@ -6,11 +6,14 @@ Usage: - %mp_ds2fmtds(sashelp.cars,work.cards) + %mp_ds2fmtds(sashelp.cars,work.cars) @param [in] libds The library.dataset to be converted @param [out] outds The dataset to create. +

Related Macros

+ @li mp_jsonout.sas + @version 9.2 @author Allan Bowe **/ diff --git a/base/mp_jsonout.sas b/base/mp_jsonout.sas index 403cea8..f36d186 100644 --- a/base/mp_jsonout.sas +++ b/base/mp_jsonout.sas @@ -48,6 +48,8 @@ @param dbg= DEPRECATED - was used to conditionally add PRETTY to proc json but this can cause line truncation in large files. +

Related Macros

+ @li mp_ds2fmtds.sas @version 9.2 @author Allan Bowe @@ -55,10 +57,11 @@ **/ -%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=PROCJSON,dbg=0 +%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 )/*/STORE SOURCE*/; %put output location=&jref; %if &action=OPEN %then %do; + OPTIONS NOBOMFILE; data _null_;file &jref encoding='utf-8'; put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"'; run; @@ -86,6 +89,64 @@ %put &sysmacroname: &ds NOT FOUND!!!; %return; %end; + %if &fmt=Y %then %do; + %put converting every variable to a formatted variable; + /* see mp_ds2fmtds.sas for source */ + proc contents noprint data=&ds + out=_data_(keep=name type length format formatl formatd varnum); + run; + proc sort; + by varnum; + run; + %local fmtds; + %let fmtds=%scan(&syslast,2,.); + /* prepare formats and varnames */ + data _null_; + set &fmtds end=last; + name=upcase(name); + /* fix formats */ + if type=2 or type=6 then do; + length fmt $49.; + if format='' then fmt=cats('$',length,'.'); + else if formatl=0 then fmt=cats(format,'.'); + else fmt=cats(format,formatl,'.'); + newlen=max(formatl,length); + end; + else do; + if format='' then fmt='best.'; + else if formatl=0 then fmt=cats(format,'.'); + else if formatd=0 then fmt=cats(format,formatl,'.'); + else fmt=cats(format,formatl,'.',formatd); + /* needs to be wide, for datetimes etc */ + newlen=max(length,formatl,24); + end; + /* 32 char unique name */ + newname='sasjs'!!substr(cats(put(md5(name),$hex32.)),1,27); + + call symputx(cats('name',_n_),name,'l'); + call symputx(cats('newname',_n_),newname,'l'); + call symputx(cats('len',_n_),newlen,'l'); + call symputx(cats('fmt',_n_),fmt,'l'); + call symputx(cats('type',_n_),type,'l'); + if last then call symputx('nobs',_n_,'l'); + run; + data &fmtds; + /* rename on entry */ + set &ds(rename=( + %local i; + %do i=1 %to &nobs; + &&name&i=&&newname&i + %end; + )); + %do i=1 %to &nobs; + length &&name&i $&&len&i; + &&name&i=left(put(&&newname&i,&&fmt&i)); + drop &&newname&i; + %end; + if _error_ then call symputx('syscc',1012); + run; + %let ds=&fmtds; + %end; /* &fmt=Y */ data _null_;file &jref mod ; put "["; call symputx('cols',0,'l'); proc sort @@ -169,4 +230,4 @@ put "}"; run; %end; -%mend; +%mend mp_jsonout; diff --git a/meta/mm_createwebservice.sas b/meta/mm_createwebservice.sas index 9bdfb39..97edbad 100644 --- a/meta/mm_createwebservice.sas +++ b/meta/mm_createwebservice.sas @@ -86,10 +86,11 @@ data _null_; put "/* Created on %sysfunc(datetime(),datetime19.) by %mf_getuser() */"; /* WEBOUT BEGIN */ put ' '; - put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=PROCJSON,dbg=0 '; + put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 '; put ')/*/STORE SOURCE*/; '; put '%put output location=&jref; '; put '%if &action=OPEN %then %do; '; + put ' OPTIONS NOBOMFILE; '; put ' data _null_;file &jref encoding=''utf-8''; '; put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; '; put ' run; '; @@ -117,6 +118,64 @@ data _null_; put ' %put &sysmacroname: &ds NOT FOUND!!!; '; put ' %return; '; put ' %end; '; + put ' %if &fmt=Y %then %do; '; + put ' %put converting every variable to a formatted variable; '; + put ' /* see mp_ds2fmtds.sas for source */ '; + put ' proc contents noprint data=&ds '; + put ' out=_data_(keep=name type length format formatl formatd varnum); '; + put ' run; '; + put ' proc sort; '; + put ' by varnum; '; + put ' run; '; + put ' %local fmtds; '; + put ' %let fmtds=%scan(&syslast,2,.); '; + put ' /* prepare formats and varnames */ '; + put ' data _null_; '; + put ' set &fmtds end=last; '; + put ' name=upcase(name); '; + put ' /* fix formats */ '; + put ' if type=2 or type=6 then do; '; + put ' length fmt $49.; '; + put ' if format='''' then fmt=cats(''$'',length,''.''); '; + put ' else if formatl=0 then fmt=cats(format,''.''); '; + put ' else fmt=cats(format,formatl,''.''); '; + put ' newlen=max(formatl,length); '; + put ' end; '; + put ' else do; '; + put ' if format='''' then fmt=''best.''; '; + put ' else if formatl=0 then fmt=cats(format,''.''); '; + put ' else if formatd=0 then fmt=cats(format,formatl,''.''); '; + put ' else fmt=cats(format,formatl,''.'',formatd); '; + put ' /* needs to be wide, for datetimes etc */ '; + put ' newlen=max(length,formatl,24); '; + put ' end; '; + put ' /* 32 char unique name */ '; + put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); '; + put ' '; + put ' call symputx(cats(''name'',_n_),name,''l''); '; + put ' call symputx(cats(''newname'',_n_),newname,''l''); '; + put ' call symputx(cats(''len'',_n_),newlen,''l''); '; + put ' call symputx(cats(''fmt'',_n_),fmt,''l''); '; + put ' call symputx(cats(''type'',_n_),type,''l''); '; + put ' if last then call symputx(''nobs'',_n_,''l''); '; + put ' run; '; + put ' data &fmtds; '; + put ' /* rename on entry */ '; + put ' set &ds(rename=( '; + put ' %local i; '; + put ' %do i=1 %to &nobs; '; + put ' &&name&i=&&newname&i '; + put ' %end; '; + put ' )); '; + put ' %do i=1 %to &nobs; '; + put ' length &&name&i $&&len&i; '; + put ' &&name&i=left(put(&&newname&i,&&fmt&i)); '; + put ' drop &&newname&i; '; + put ' %end; '; + put ' if _error_ then call symputx(''syscc'',1012); '; + put ' run; '; + put ' %let ds=&fmtds; '; + put ' %end; /* &fmt=Y */ '; put ' data _null_;file &jref mod ; '; put ' put "["; call symputx(''cols'',0,''l''); '; put ' proc sort '; @@ -200,7 +259,7 @@ data _null_; put ' put "}"; '; put ' run; '; put '%end; '; - put '%mend; '; + put '%mend mp_jsonout; '; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y); '; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug '; put ' sasjs_tables; '; diff --git a/tests/base/mp_jsonout.test.sas b/tests/base/mp_jsonout.test.sas new file mode 100644 index 0000000..156c38e --- /dev/null +++ b/tests/base/mp_jsonout.test.sas @@ -0,0 +1,42 @@ +/** + @file + @brief Testing mp_jsonout.sas macro + +

SAS Macros

+ @li mp_jsonout.sas + @li mp_assert.sas + +**/ + +filename webref temp; + +data demo; + dtval=date(); + format dtval date9.; + compare=put(date(),date9.); + call symputx('compare',compare); +run; + +%mp_jsonout(OPEN,jref=webref) +%mp_jsonout(OBJ,demo,jref=webref,fmt=Y) +%mp_jsonout(CLOSE,jref=webref) + +data _null_; + infile webref; + input; + putlog _infile_; +run; + +libname web JSON fileref=webref; +%let dtval=0; +data work.test; + set web.demo; + call symputx('dtval',dtval); +run; + + +%mp_assert( + iftrue=(&dtval=&compare), + desc=Checking tables were created successfully, + outds=work.test_results +) \ No newline at end of file diff --git a/viya/mv_createwebservice.sas b/viya/mv_createwebservice.sas index da2ddac..8b91519 100644 --- a/viya/mv_createwebservice.sas +++ b/viya/mv_createwebservice.sas @@ -237,10 +237,11 @@ data _null_; put "/* Created on %sysfunc(datetime(),datetime19.) by &sysuserid */"; /* WEBOUT BEGIN */ put ' '; - put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=PROCJSON,dbg=0 '; + put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 '; put ')/*/STORE SOURCE*/; '; put '%put output location=&jref; '; put '%if &action=OPEN %then %do; '; + put ' OPTIONS NOBOMFILE; '; put ' data _null_;file &jref encoding=''utf-8''; '; put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; '; put ' run; '; @@ -268,6 +269,64 @@ data _null_; put ' %put &sysmacroname: &ds NOT FOUND!!!; '; put ' %return; '; put ' %end; '; + put ' %if &fmt=Y %then %do; '; + put ' %put converting every variable to a formatted variable; '; + put ' /* see mp_ds2fmtds.sas for source */ '; + put ' proc contents noprint data=&ds '; + put ' out=_data_(keep=name type length format formatl formatd varnum); '; + put ' run; '; + put ' proc sort; '; + put ' by varnum; '; + put ' run; '; + put ' %local fmtds; '; + put ' %let fmtds=%scan(&syslast,2,.); '; + put ' /* prepare formats and varnames */ '; + put ' data _null_; '; + put ' set &fmtds end=last; '; + put ' name=upcase(name); '; + put ' /* fix formats */ '; + put ' if type=2 or type=6 then do; '; + put ' length fmt $49.; '; + put ' if format='''' then fmt=cats(''$'',length,''.''); '; + put ' else if formatl=0 then fmt=cats(format,''.''); '; + put ' else fmt=cats(format,formatl,''.''); '; + put ' newlen=max(formatl,length); '; + put ' end; '; + put ' else do; '; + put ' if format='''' then fmt=''best.''; '; + put ' else if formatl=0 then fmt=cats(format,''.''); '; + put ' else if formatd=0 then fmt=cats(format,formatl,''.''); '; + put ' else fmt=cats(format,formatl,''.'',formatd); '; + put ' /* needs to be wide, for datetimes etc */ '; + put ' newlen=max(length,formatl,24); '; + put ' end; '; + put ' /* 32 char unique name */ '; + put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); '; + put ' '; + put ' call symputx(cats(''name'',_n_),name,''l''); '; + put ' call symputx(cats(''newname'',_n_),newname,''l''); '; + put ' call symputx(cats(''len'',_n_),newlen,''l''); '; + put ' call symputx(cats(''fmt'',_n_),fmt,''l''); '; + put ' call symputx(cats(''type'',_n_),type,''l''); '; + put ' if last then call symputx(''nobs'',_n_,''l''); '; + put ' run; '; + put ' data &fmtds; '; + put ' /* rename on entry */ '; + put ' set &ds(rename=( '; + put ' %local i; '; + put ' %do i=1 %to &nobs; '; + put ' &&name&i=&&newname&i '; + put ' %end; '; + put ' )); '; + put ' %do i=1 %to &nobs; '; + put ' length &&name&i $&&len&i; '; + put ' &&name&i=left(put(&&newname&i,&&fmt&i)); '; + put ' drop &&newname&i; '; + put ' %end; '; + put ' if _error_ then call symputx(''syscc'',1012); '; + put ' run; '; + put ' %let ds=&fmtds; '; + put ' %end; /* &fmt=Y */ '; put ' data _null_;file &jref mod ; '; put ' put "["; call symputx(''cols'',0,''l''); '; put ' proc sort '; @@ -351,7 +410,7 @@ data _null_; put ' put "}"; '; put ' run; '; put '%end; '; - put '%mend; '; + put '%mend mp_jsonout; '; put '%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y); '; put '%global _webin_file_count _webin_fileuri _debug _omittextlog _webin_name '; put ' sasjs_tables SYS_JES_JOB_URI; ';