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

Compare commits

...

21 Commits

Author SHA1 Message Date
Allan Bowe
f420ac2abf Merge pull request #321 from sasjs/issue320
fix: full escaping of syswarningtext and syserrortext. Closes #320
2022-12-04 18:22:52 +00:00
Allan Bowe
7edec1ad8a fix: full escaping of syswarningtext and syserrortext. Closes #320 2022-12-04 18:18:15 +00:00
Allan Bowe
62d7bce249 feat: adding nobs limit to mp_gitlog 2022-12-04 17:23:53 +00:00
munja
fe6c9a793b chore: fixing saspac build 2022-11-30 22:21:58 +01:00
Allan Bowe
8e13943356 Merge pull request #319 from sasjs/gitbranch
3 new macros (and tests) for the core library
2022-11-30 20:43:54 +00:00
munja
04df9600e0 chore: updating all.sas 2022-11-30 21:43:28 +01:00
munja
e2b0aabfa4 feat: mp_gitlog and associated test/docs 2022-11-30 21:35:49 +01:00
munja
c52a623630 feat: new mf_getgitbranch macro (and test) 2022-11-30 20:15:10 +01:00
munja
cf348e8016 feat: new mf_readfile macro (and test) 2022-11-30 20:06:11 +01:00
Allan Bowe
6502fc4982 Merge pull request #318 from sasjs/hashfix
fix: ensuring mp_hashdirectory will output an empty dataset when the …
2022-10-27 21:41:13 +01:00
Allan Bowe
ef574f6319 fix: ensuring mp_hashdirectory will output an empty dataset when the target directory does not exist. Updated tests and documentation also 2022-10-27 20:38:41 +00:00
munja
5b251006cd chore(docs): fix descriptions for mp_git* macros 2022-10-22 22:24:51 +01:00
munja
b353acec47 chore: stripping the v from the tag for sas packages deploy 2022-10-21 11:33:04 +01:00
munja
8b148c3916 chore: docfix for mp_gitstatus and yaml fix for npx 2022-10-20 17:35:25 +01:00
munja
2efdcec54c chore: fix failing workflow run 2022-10-20 17:32:59 +01:00
Allan Bowe
f832e93f4b Merge pull request #317 from sasjs/gitfuncs
feat: two new macros (mp_gitadd and mp_gitstatus) with corresponding …
2022-10-20 17:16:26 +01:00
munja
f37c2e5867 feat: two new macros (mp_gitadd and mp_gitstatus) with corresponding tests, also a new utility program for deploying the library as a SAS PACKAGE 2022-10-20 17:11:43 +01:00
Allan Bowe
6f8ec5d5a8 Merge pull request #316 from sasjs/gitinfo
feat: new gitreleaseinfo macro and associated test
2022-10-15 17:12:24 +01:00
munja
6521ade608 chore: generating all.sas 2022-10-15 17:11:58 +01:00
munja
2666bbc85e feat: new gitreleaseinfo macro and associated test 2022-10-15 17:09:26 +01:00
Allan Bowe
ee35f47f4f feat: new mfv_existsashdat() macro for checking whether a dataset exists in persistent storage 2022-10-07 13:43:41 +00:00
27 changed files with 1823 additions and 36 deletions

View File

@@ -19,3 +19,10 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: SAS Packages Release
run: |
npx @sasjs/cli compile job -s sasjs/utils/create_sas_package.sas -o sasjsbuild
# this part depends on https://github.com/sasjs/server/issues/307
# sasjs run sasjsbuild/jobs/utils/create_sas_package.sas -t sas9

4
.gitignore vendored
View File

@@ -10,4 +10,6 @@ sasjsresults/
mc_*
# ignore .env files as they can contain sasjs access tokens
*.env*
*.env*
~

689
all.sas
View File

@@ -781,6 +781,43 @@ or %index(&pgm,/tests/testteardown)
/* send them out without spaces or quote markers */
%do;%unquote(%upcase(&fmt))%end;
%mend mf_getfmtname;/**
@file
@brief Retrieves the current branch from a local GIT repo
@details In a local git repository, the current branch is always available in
the `.git/HEAD` file in a format like this: `ref: refs/heads/master`
This macro simply reads the file and returns the last word (eg `master`).
Example usage:
%let gitdir=%sysfunc(pathname(work))/core;
%let repo=https://github.com/sasjs/core;
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&gitdir));
%put The current branch is %mf_getgitbranch(&gitdir);
@param [in] gitdir The directory containing the GIT repository
<h4> SAS Macros </h4>
@li mf_readfile.sas
<h4> Related Macros </h4>
@li mp_gitadd.sas
@li mp_gitlog.sas
@li mp_gitreleaseinfo.sas
@li mp_gitstatus.sas
@version 9.2
@author Allan Bowe
**/
%macro mf_getgitbranch(gitdir
)/*/STORE SOURCE*/;
%scan(%mf_readfile(&gitdir/.git/HEAD),-1)
%mend mf_getgitbranch;
/**
@file
@brief retrieves a key value pair from a control dataset
@details By default, control dataset is work.mp_setkeyvalue. Usage:
@@ -1902,6 +1939,69 @@ Usage:
)/*/STORE SOURCE*/;
%mf_getattrn(&libds,NLOBS)
%mend mf_nobs;/**
@file
@brief Reads the first line of a file using pure macro
@details Reads the first line of a file and returns it. Future versions may
read each line into a macro variable array.
Generally, reading data into macro variables is not great as certain
nonprintable characters (such as CR, LF) may be dropped in the conversion.
Usage:
%mf_writefile(&sasjswork/myfile.txt,l1=some content,l2=more content)
%put %mf_readfile(&sasjswork/myfile.txt);
@param [in] fpath Full path to file to be read
<h4> Related Macros </h4>
@li mf_deletefile.sas
@li mf_writefile.sas
@li mf_readfile.test.sas
@version 9.2
@author Allan Bowe
**/
/** @cond */
%macro mf_readfile(fpath
)/*/STORE SOURCE*/;
%local fref rc fid fcontent;
/* check file exists */
%if %sysfunc(filename(fref,&fpath)) ne 0 %then %do;
%put &=fref &=fpath;
%put %str(ERR)OR: %sysfunc(sysmsg());
%return;
%end;
%let fid=%sysfunc(fopen(&fref,I));
%if &fid=0 %then %do;
%put %str(ERR)OR: %sysfunc(sysmsg());
%return;
%end;
%if %sysfunc(fread(&fid)) = 0 %then %do;
%let rc=%sysfunc(fget(&fid,fcontent,65534));
&fcontent
%end;
/*
%do %while(%sysfunc(fread(&fid)) = 0);
%let rc=%sysfunc(fget(&fid,fcontent,65534));
&fcontent
%end;
*/
%let rc=%sysfunc(fclose(&fid));
%let rc=%sysfunc(filename(&fref));
%mend mf_readfile;
/** @endcond */
/**
@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
@@ -4608,8 +4708,7 @@ data &out_ds(compress=no
if did=0 then do;
putlog "NOTE: This directory is empty, or does not exist - &path";
msg=sysmsg();
put msg;
put _all_;
put (_all_)(=);
stop;
end;
/* attribute is OS-dependent - could be "Directory" or "Directory Name" */
@@ -8168,6 +8267,296 @@ create table &outds as
)
%mend mp_getpk;
/**
@file
@brief Stages files in a GIT repo
@details Uses the output dataset from mp_gitstatus.sas to determine the files
that should be staged.
If `STAGED ne "TRUE"` then the file is staged.
Usage:
%let dir=%sysfunc(pathname(work))/core;
%let repo=https://github.com/sasjs/core;
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&dir));
%mf_writefile(&dir/somefile.txt,l1=some content)
%mf_deletefile(&dir/package.json)
%mp_gitstatus(&dir,outds=work.gitstatus)
%mp_gitadd(&dir,inds=work.gitstatus)
@param [in] gitdir The directory containing the GIT repository
@param [in] inds= (work.mp_gitadd) The input dataset with the list of files
to stage. Will accept the output from mp_gitstatus(), else just use a table
with the following columns:
@li path $1024 - relative path to the file in the repo
@li staged $32 - whether the file is staged (TRUE or FALSE)
@li status $64 - either new, deleted, or modified
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
<h4> Related Files </h4>
@li mp_gitadd.test.sas
@li mp_gitstatus.sas
**/
%macro mp_gitadd(gitdir,inds=work.mp_gitadd,mdebug=0);
data _null_;
set &inds;
if STAGED ne "TRUE";
rc=git_index_add("&gitdir",cats(path),status);
if rc ne 0 or &mdebug=1 then put rc=;
run;
%mend mp_gitadd;
/**
@file
@brief Creates a dataset with the commit history of a local repository
@details Returns the commit history from a local repository. The name of the
branch is also returned.
More details here:
https://documentation.sas.com/doc/ko/pgmsascdc/v_033/lefunctionsref/n1qo5miyvry1nen111js203hlwrh.htm
Usage:
%let gitdir=%sysfunc(pathname(work))/core;
%let repo=https://github.com/sasjs/core;
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&dir));
%mp_gitlog(&gitdir,outds=work.mp_gitlog)
@param [in] gitdir The directory containing the GIT repository
@param [in] filter= (BRANCHONLY) To return only the commits for the current
branch, use BRANCHONLY (the default). Anything else will return the entire
commit history.
@param [out] outds= (work.mp_gitlog) The output dataset to create.
All vars are $128 except `message` which is $4000.
@li author returns the author who submitted the commit.
@li children_ids returns a list of the children commit IDs
@li committer returns the name of the committer.
@li committer_email returns the email of the committer.
@li email returns the email of the commit author.
@li id returns the commit ID of the commit object.
@li in_current_branch returns "TRUE" or "FALSE" to indicate if the commit is
in the current branch.
@li message returns the commit message.
@li parent_ids returns a list of the parent commit IDs.
@li stash returns "TRUE" or "FALSE" to indicate if the commit is a stash
commit.
@li time returns the time of the commit as numeric string
@li commit_time_num time of the commit as numeric SAS datetime
@li commit_time_str the commit_time_num variable cast as string
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
@param [in] nobs= (0) Set to an integer greater than 0 to restrict the number
of rows returned
<h4> SAS Macros </h4>
@li mf_getgitbranch.sas
<h4> Related Files </h4>
@li mp_gitadd.sas
@li mp_gitreleaseinfo.sas
@li mp_gitstatus.sas
**/
%macro mp_gitlog(gitdir,outds=work.mp_gitlog,mdebug=0,filter=BRANCHONLY,nobs=0);
%local varlist i var;
%let varlist=author children_ids committer committer_email email id
in_current_branch parent_ids stash time ;
data &outds;
LENGTH gitdir branch $ 1024 message $4000 &varlist $128 commit_time_num 8.
commit_time_str $32;
call missing (of _all_);
branch="%mf_getgitbranch(&gitdir)";
gitdir=symget('gitdir');
rc=git_status_free(trim(gitdir));
if rc=-1 then do;
put "The libgit2 library is unavailable and no Git operations can be used.";
put "See: https://stackoverflow.com/questions/74082874";
stop;
end;
else if rc=-2 then do;
put "The libgit2 library is available, but the status function failed.";
put "See the log for details.";
stop;
end;
entries=git_commit_log(trim(gitdir));
do n=1 to entries;
%do i=1 %to %sysfunc(countw(&varlist message));
%let var=%scan(&varlist message,&i,%str( ));
rc=git_commit_get(n,trim(gitdir),"&var",&var);
%end;
/* convert unix time to SAS time - https://4gl.uk/corelink0 */
/* Number of seconds between 01JAN1960 and 01JAN1970: 315619200 */
format commit_time_num datetime19.;
commit_time_num=sum(input(cats(time),best.),315619200);
commit_time_str=put(commit_time_num,datetime19.);
%if &mdebug=1 %then %do;
putlog (_all_)(=);
%end;
if "&filter"="BRANCHONLY" then do;
if cats(in_current_branch)='TRUE' then output;
end;
else output;
%if &nobs>0 %then %do;
if n ge &nobs then stop;
%end;
end;
rc=git_commit_free(trim(gitdir));
keep gitdir branch &varlist message time commit_time_num commit_time_str;
run;
%mend mp_gitlog;
/**
@file
@brief Pulls latest release info from a GIT repository
@details Useful for grabbing the latest version number or other attributes
from a GIT server. Supported providers are GitLab and GitHub. Pull requests
are welcome if you'd like to see additional providers!
Note that each provider provides slightly different JSON output. Therefore
the macro simply extracts the JSON and assigns the libname (using the JSON
engine).
Example usage (eg, to grab latest release version from github):
%mp_gitreleaseinfo(GITHUB,sasjs/core,outlib=mylibref)
data _null_;
set mylibref.root;
putlog TAG_NAME=;
run;
@param [in] provider The GIT provider for the release info. Accepted values:
@li GITLAB
@li GITHUB - Tables include root, assets, author, alldata
@param [in] project The link to the repository. This has different formats
depending on the vendor:
@li GITHUB - org/repo, eg sasjs/core
@li GITLAB - project, eg 1343223
@param [in] server= (0) If your repo is self-hosted, then provide the domain
here. Otherwise it will default to the provider domain (eg gitlab.com).
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
@param [out] outlib= (GITREL) The JSON-engine libref to be created, which will
point at the returned JSON
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
<h4> Related Files </h4>
@li mp_gitreleaseinfo.test.sas
**/
%macro mp_gitreleaseinfo(provider,project,server=0,outlib=GITREL,mdebug=0);
%local url fref;
%let provider=%upcase(&provider);
%if &provider=GITHUB %then %do;
%if "&server"="0" %then %let server=https://api.github.com;
%let url=&server/repos/&project/releases/latest;
%end;
%else %if &provider=GITLAB %then %do;
%if "&server"="0" %then %let server=https://gitlab.com;
%let url=&server/api/v4/projects/&project/releases;
%end;
%let fref=%mf_getuniquefileref();
proc http method='GET' out=&fref url="&url";
%if &mdebug=1 %then %do;
debug level = 3;
%end;
run;
libname &outlib JSON fileref=&fref;
%if &mdebug=1 %then %do;
data _null_;
infile &fref;
input;
putlog _infile_;
run;
%end;
%mend mp_gitreleaseinfo;
/**
@file
@brief Creates a dataset with the output from `GIT_STATUS()`
@details Uses `git_status()` to fetch the number of changed files, then
iterates with `git_status_get()`, inserting all attributes into an output
dataset.
Usage:
%let dir=%sysfunc(pathname(work))/core;
%let repo=https://github.com/sasjs/core;
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&dir));
%mf_writefile(&dir/somefile.txt,l1=some content)
%mf_deletefile(&dir/package.json)
%mp_gitstatus(&dir,outds=work.gitstatus)
More info on these functions is in this [helpful paper]
(https://www.sas.com/content/dam/SAS/support/en/sas-global-forum-proceedings/2019/3057-2019.pdf)
by Danny Zimmerman.
@param [in] gitdir The directory containing the GIT repository
@param [out] outds= (work.git_status) The output dataset to create. Vars:
@li gitdir $1024 - directory of repo
@li path $1024 - relative path to the file in the repo
@li staged $32 - whether the file is staged (TRUE or FALSE)
@li status $64 - either new, deleted, or modified
@li cnt - number of files
@li n - the "nth" file in the list from git_status()
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
<h4> Related Files </h4>
@li mp_gitstatus.test.sas
@li mp_gitadd.sas
**/
%macro mp_gitstatus(gitdir,outds=work.mp_gitstatus,mdebug=0);
data &outds;
LENGTH gitdir path $ 1024 STATUS $ 64 STAGED $ 32;
call missing (of _all_);
gitdir=symget('gitdir');
cnt=git_status(trim(gitdir));
if cnt=-1 then do;
put "The libgit2 library is unavailable and no Git operations can be used.";
put "See: https://stackoverflow.com/questions/74082874";
end;
else if cnt=-2 then do;
put "The libgit2 library is available, but the status function failed.";
put "See the log for details.";
end;
else do n=1 to cnt;
rc=GIT_STATUS_GET(n,gitdir,'PATH',path);
rc=GIT_STATUS_GET(n,gitdir,'STAGED',staged);
rc=GIT_STATUS_GET(n,gitdir,'STATUS',status);
output;
%if &mdebug=1 %then %do;
putlog (_all_)(=);
%end;
end;
rc=git_status_free(trim(gitdir));
drop rc cnt;
run;
%mend mp_gitstatus;
/**
@file
@brief Performs a text substitution on a file
@@ -8655,7 +9044,7 @@ run;
create a hash for each directory also.
This makes use of the new `hashing_file()` and `hashing` functions, available
since 9.4m6. Interestingly, these can even be used in pure macro, eg:
since 9.4m6. Interestingly, those functions can be used in pure macro, eg:
%put %sysfunc(hashing_file(md5,/path/to/file.blob,0));
@@ -8680,8 +9069,9 @@ run;
@li If a folder contains other folders, start from the bottom of the tree -
the folder hashes cascade upwards so you know immediately if there is a
change in a sub/sub directory
@li If the folder has no content (empty) then it is ignored. No hash created.
@li If a subfolder has no content (empty) then it is ignored. No hash created.
@li If the file is empty, it is also ignored / no hash created.
@li If the target directory (&inloc) is empty, &outds will also be empty
<h4> SAS Macros </h4>
@li mp_dirlist.sas
@@ -8722,7 +9112,7 @@ run;
iftrue=%str(1=1)
)/*/STORE SOURCE*/;
%local curlevel tempds ;
%local curlevel tempds maxlevel;
%if not(%eval(%unquote(&iftrue))) %then %return;
@@ -8758,6 +9148,7 @@ proc sort data=&outds ;
by descending level directory file_path;
run;
%let maxlevel=0;
data _null_;
set &outds;
call symputx('maxlevel',level,'l');
@@ -16037,7 +16428,24 @@ data _null_;
put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
put ' put ",""SYSCC"" : ""&syscc"" "; ';
put ' put ",""SYSENCODING"" : ""&sysencoding"" "; ';
put ' syserrortext=cats(''"'',tranwrd(symget(''syserrortext''),''"'',''\"''),''"''); ';
put ' syserrortext=cats(symget(''syserrortext'')); ';
put ' if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' syserrortext=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,syserrortext) ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
put ' put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" "; ';
@@ -16050,7 +16458,24 @@ data _null_;
put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
put ' put '',"SYSVLONG" : '' sysvlong; ';
put ' syswarningtext=cats(''"'',tranwrd(symget(''syswarningtext''),''"'',''\"''),''"''); ';
put ' syswarningtext=cats(symget(''syswarningtext'')); ';
put ' if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' syswarningtext=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,syswarningtext) ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; ';
put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
put ' length memsize $32; ';
@@ -19683,7 +20108,24 @@ run;
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSENCODING"" : ""&sysencoding"" ";
syserrortext=cats('"',tranwrd(symget('syserrortext'),'"','\"'),'"');
syserrortext=cats(symget('syserrortext'));
if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do;
syserrortext='"'!!trim(
prxchange('s/"/\\"/',-1, /* double quote */
prxchange('s/\x0A/\n/',-1, /* new line */
prxchange('s/\x0D/\r/',-1, /* carriage return */
prxchange('s/\x09/\\t/',-1, /* tab */
prxchange('s/\x00/\\u0000/',-1, /* NUL */
prxchange('s/\x0E/\\u000E/',-1, /* SS */
prxchange('s/\x0F/\\u000F/',-1, /* SF */
prxchange('s/\x01/\\u0001/',-1, /* SOH */
prxchange('s/\x02/\\u0002/',-1, /* STX */
prxchange('s/\x10/\\u0010/',-1, /* DLE */
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
prxchange('s/\\/\\\\/',-1,syserrortext)
)))))))))))))!!'"';
end;
put ',"SYSERRORTEXT" : ' syserrortext;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";
@@ -19696,7 +20138,24 @@ run;
put ",""SYSUSERID"" : ""&sysuserid"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
syswarningtext=cats('"',tranwrd(symget('syswarningtext'),'"','\"'),'"');
syswarningtext=cats(symget('syswarningtext'));
if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do;
syswarningtext='"'!!trim(
prxchange('s/"/\\"/',-1, /* double quote */
prxchange('s/\x0A/\n/',-1, /* new line */
prxchange('s/\x0D/\r/',-1, /* carriage return */
prxchange('s/\x09/\\t/',-1, /* tab */
prxchange('s/\x00/\\u0000/',-1, /* NUL */
prxchange('s/\x0E/\\u000E/',-1, /* SS */
prxchange('s/\x0F/\\u000F/',-1, /* SF */
prxchange('s/\x01/\\u0001/',-1, /* SOH */
prxchange('s/\x02/\\u0002/',-1, /* STX */
prxchange('s/\x10/\\u0010/',-1, /* DLE */
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
prxchange('s/\\/\\\\/',-1,syswarningtext)
)))))))))))))!!'"';
end;
put ',"SYSWARNINGTEXT" : ' syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
length memsize $32;
@@ -21047,7 +21506,24 @@ data _null_;
put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
put ' put ",""SYSCC"" : ""&syscc"" "; ';
put ' put ",""SYSENCODING"" : ""&sysencoding"" "; ';
put ' syserrortext=cats(''"'',tranwrd(symget(''syserrortext''),''"'',''\"''),''"''); ';
put ' syserrortext=cats(symget(''syserrortext'')); ';
put ' if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' syserrortext=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,syserrortext) ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
put ' SYSHOSTINFOLONG=quote(trim(symget(''SYSHOSTINFOLONG''))); ';
put ' put '',"SYSHOSTINFOLONG" : '' SYSHOSTINFOLONG; ';
@@ -21063,7 +21539,24 @@ data _null_;
put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
put ' put '',"SYSVLONG" : '' sysvlong; ';
put ' syswarningtext=cats(''"'',tranwrd(symget(''syswarningtext''),''"'',''\"''),''"''); ';
put ' syswarningtext=cats(symget(''syswarningtext'')); ';
put ' if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' syswarningtext=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,syswarningtext) ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; ';
put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
put ' length memsize $32; ';
@@ -22044,7 +22537,24 @@ run;
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSENCODING"" : ""&sysencoding"" ";
syserrortext=cats('"',tranwrd(symget('syserrortext'),'"','\"'),'"');
syserrortext=cats(symget('syserrortext'));
if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do;
syserrortext='"'!!trim(
prxchange('s/"/\\"/',-1, /* double quote */
prxchange('s/\x0A/\n/',-1, /* new line */
prxchange('s/\x0D/\r/',-1, /* carriage return */
prxchange('s/\x09/\\t/',-1, /* tab */
prxchange('s/\x00/\\u0000/',-1, /* NUL */
prxchange('s/\x0E/\\u000E/',-1, /* SS */
prxchange('s/\x0F/\\u000F/',-1, /* SF */
prxchange('s/\x01/\\u0001/',-1, /* SOH */
prxchange('s/\x02/\\u0002/',-1, /* STX */
prxchange('s/\x10/\\u0010/',-1, /* DLE */
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
prxchange('s/\\/\\\\/',-1,syserrortext)
)))))))))))))!!'"';
end;
put ',"SYSERRORTEXT" : ' syserrortext;
SYSHOSTINFOLONG=quote(trim(symget('SYSHOSTINFOLONG')));
put ',"SYSHOSTINFOLONG" : ' SYSHOSTINFOLONG;
@@ -22060,7 +22570,24 @@ run;
put ",""SYSUSERID"" : ""&sysuserid"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
syswarningtext=cats('"',tranwrd(symget('syswarningtext'),'"','\"'),'"');
syswarningtext=cats(symget('syswarningtext'));
if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do;
syswarningtext='"'!!trim(
prxchange('s/"/\\"/',-1, /* double quote */
prxchange('s/\x0A/\n/',-1, /* new line */
prxchange('s/\x0D/\r/',-1, /* carriage return */
prxchange('s/\x09/\\t/',-1, /* tab */
prxchange('s/\x00/\\u0000/',-1, /* NUL */
prxchange('s/\x0E/\\u000E/',-1, /* SS */
prxchange('s/\x0F/\\u000F/',-1, /* SF */
prxchange('s/\x01/\\u0001/',-1, /* SOH */
prxchange('s/\x02/\\u0002/',-1, /* STX */
prxchange('s/\x10/\\u0010/',-1, /* DLE */
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
prxchange('s/\\/\\\\/',-1,syswarningtext)
)))))))))))))!!'"';
end;
put ',"SYSWARNINGTEXT" : ' syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
length memsize $32;
@@ -22170,6 +22697,66 @@ run;
%end;
%mend mfv_existfolder;/**
@file mfv_existsashdat.sas
@brief Checks whether a CAS sashdat dataset exists in persistent storage.
@details Can be used in open code, eg as follows:
%if %mfv_existsashdat(libds=casuser.sometable) %then %put yes it does!;
The function uses `dosubl()` to run the `table.fileinfo` action, for the
specified library, filtering for `*.sashdat` tables. The results are stored
in a WORK table (&outprefix._&lib). If that table already exists, it is
queried instead, to avoid the dosubl() performance hit.
To force a rescan, just use a new `&outprefix` value, or delete the table(s)
before running the function.
@param libds library.dataset
@param outprefix= (work.mfv_existsashdat) Used to store the current HDATA
tables to improve subsequent query performance. This reference is a prefix
and is converted to `&prefix._{libref}`
@return output returns 1 or 0
@version 0.2
@author Mathieu Blauw
**/
%macro mfv_existsashdat(libds,outprefix=work.mfv_existsashdat
);
%local rc dsid name lib ds;
%let lib=%upcase(%scan(&libds,1,'.'));
%let ds=%upcase(%scan(&libds,-1,'.'));
/* if table does not exist, create it */
%if %sysfunc(exist(&outprefix._&lib)) ne 1 %then %do;
%let rc=%sysfunc(dosubl(%nrstr(
/* Read in table list (once per &lib per session) */
proc cas;
table.fileinfo result=source_list /caslib="&lib";
val=findtable(source_list);
saveresult val dataout=&outprefix._&lib;
quit;
/* Only keep name, without file extension */
data &outprefix._&lib;
set &outprefix._&lib(where=(Name like '%.sashdat') keep=Name);
Name=upcase(scan(Name,1,'.'));
run;
)));
%end;
/* Scan table for hdat existence */
%let dsid=%sysfunc(open(&outprefix._&lib(where=(name="&ds"))));
%syscall set(dsid);
%let rc = %sysfunc(fetch(&dsid));
%let rc = %sysfunc(close(&dsid));
/* Return result */
%if "%trim(&name)"="%trim(&ds)" %then 1;
%else 0;
%mend mfv_existsashdat;
/**
@file
@brief Creates a file in SAS Drive
@details Creates a file in SAS Drive and adds the appropriate content type.
@@ -23578,7 +24165,24 @@ data _null_;
put ' put '',"SYS_JES_JOB_URI" : '' SYS_JES_JOB_URI ; ';
put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
put ' put ",""SYSCC"" : ""&syscc"" "; ';
put ' syserrortext=cats(''"'',tranwrd(symget(''syserrortext''),''"'',''\"''),''"''); ';
put ' syserrortext=cats(symget(''syserrortext'')); ';
put ' if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' syserrortext=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,syserrortext) ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
put ' put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" "; ';
@@ -23591,7 +24195,24 @@ data _null_;
put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
put ' put '',"SYSVLONG" : '' sysvlong; ';
put ' syswarningtext=cats(''"'',tranwrd(symget(''syswarningtext''),''"'',''\"''),''"''); ';
put ' syswarningtext=cats(symget(''syswarningtext'')); ';
put ' if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' syswarningtext=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,syswarningtext) ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; ';
put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
put ' length memsize $32; ';
@@ -27385,7 +28006,24 @@ filename &fref1 clear;
put ',"SYS_JES_JOB_URI" : ' SYS_JES_JOB_URI ;
put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""SYSCC"" : ""&syscc"" ";
syserrortext=cats('"',tranwrd(symget('syserrortext'),'"','\"'),'"');
syserrortext=cats(symget('syserrortext'));
if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do;
syserrortext='"'!!trim(
prxchange('s/"/\\"/',-1, /* double quote */
prxchange('s/\x0A/\n/',-1, /* new line */
prxchange('s/\x0D/\r/',-1, /* carriage return */
prxchange('s/\x09/\\t/',-1, /* tab */
prxchange('s/\x00/\\u0000/',-1, /* NUL */
prxchange('s/\x0E/\\u000E/',-1, /* SS */
prxchange('s/\x0F/\\u000F/',-1, /* SF */
prxchange('s/\x01/\\u0001/',-1, /* SOH */
prxchange('s/\x02/\\u0002/',-1, /* STX */
prxchange('s/\x10/\\u0010/',-1, /* DLE */
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
prxchange('s/\\/\\\\/',-1,syserrortext)
)))))))))))))!!'"';
end;
put ',"SYSERRORTEXT" : ' syserrortext;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";
@@ -27398,7 +28036,24 @@ filename &fref1 clear;
put ",""SYSUSERID"" : ""&sysuserid"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
syswarningtext=cats('"',tranwrd(symget('syswarningtext'),'"','\"'),'"');
syswarningtext=cats(symget('syswarningtext'));
if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do;
syswarningtext='"'!!trim(
prxchange('s/"/\\"/',-1, /* double quote */
prxchange('s/\x0A/\n/',-1, /* new line */
prxchange('s/\x0D/\r/',-1, /* carriage return */
prxchange('s/\x09/\\t/',-1, /* tab */
prxchange('s/\x00/\\u0000/',-1, /* NUL */
prxchange('s/\x0E/\\u000E/',-1, /* SS */
prxchange('s/\x0F/\\u000F/',-1, /* SF */
prxchange('s/\x01/\\u0001/',-1, /* SOH */
prxchange('s/\x02/\\u0002/',-1, /* STX */
prxchange('s/\x10/\\u0010/',-1, /* DLE */
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
prxchange('s/\\/\\\\/',-1,syswarningtext)
)))))))))))))!!'"';
end;
put ',"SYSWARNINGTEXT" : ' syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
length memsize $32;

37
base/mf_getgitbranch.sas Normal file
View File

@@ -0,0 +1,37 @@
/**
@file
@brief Retrieves the current branch from a local GIT repo
@details In a local git repository, the current branch is always available in
the `.git/HEAD` file in a format like this: `ref: refs/heads/master`
This macro simply reads the file and returns the last word (eg `master`).
Example usage:
%let gitdir=%sysfunc(pathname(work))/core;
%let repo=https://github.com/sasjs/core;
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&gitdir));
%put The current branch is %mf_getgitbranch(&gitdir);
@param [in] gitdir The directory containing the GIT repository
<h4> SAS Macros </h4>
@li mf_readfile.sas
<h4> Related Macros </h4>
@li mp_gitadd.sas
@li mp_gitlog.sas
@li mp_gitreleaseinfo.sas
@li mp_gitstatus.sas
@version 9.2
@author Allan Bowe
**/
%macro mf_getgitbranch(gitdir
)/*/STORE SOURCE*/;
%scan(%mf_readfile(&gitdir/.git/HEAD),-1)
%mend mf_getgitbranch;

63
base/mf_readfile.sas Normal file
View File

@@ -0,0 +1,63 @@
/**
@file
@brief Reads the first line of a file using pure macro
@details Reads the first line of a file and returns it. Future versions may
read each line into a macro variable array.
Generally, reading data into macro variables is not great as certain
nonprintable characters (such as CR, LF) may be dropped in the conversion.
Usage:
%mf_writefile(&sasjswork/myfile.txt,l1=some content,l2=more content)
%put %mf_readfile(&sasjswork/myfile.txt);
@param [in] fpath Full path to file to be read
<h4> Related Macros </h4>
@li mf_deletefile.sas
@li mf_writefile.sas
@li mf_readfile.test.sas
@version 9.2
@author Allan Bowe
**/
/** @cond */
%macro mf_readfile(fpath
)/*/STORE SOURCE*/;
%local fref rc fid fcontent;
/* check file exists */
%if %sysfunc(filename(fref,&fpath)) ne 0 %then %do;
%put &=fref &=fpath;
%put %str(ERR)OR: %sysfunc(sysmsg());
%return;
%end;
%let fid=%sysfunc(fopen(&fref,I));
%if &fid=0 %then %do;
%put %str(ERR)OR: %sysfunc(sysmsg());
%return;
%end;
%if %sysfunc(fread(&fid)) = 0 %then %do;
%let rc=%sysfunc(fget(&fid,fcontent,65534));
&fcontent
%end;
/*
%do %while(%sysfunc(fread(&fid)) = 0);
%let rc=%sysfunc(fget(&fid,fcontent,65534));
&fcontent
%end;
*/
%let rc=%sysfunc(fclose(&fid));
%let rc=%sysfunc(filename(&fref));
%mend mf_readfile;
/** @endcond */

View File

@@ -101,8 +101,7 @@ data &out_ds(compress=no
if did=0 then do;
putlog "NOTE: This directory is empty, or does not exist - &path";
msg=sysmsg();
put msg;
put _all_;
put (_all_)(=);
stop;
end;
/* attribute is OS-dependent - could be "Directory" or "Directory Name" */

45
base/mp_gitadd.sas Normal file
View File

@@ -0,0 +1,45 @@
/**
@file
@brief Stages files in a GIT repo
@details Uses the output dataset from mp_gitstatus.sas to determine the files
that should be staged.
If `STAGED ne "TRUE"` then the file is staged.
Usage:
%let dir=%sysfunc(pathname(work))/core;
%let repo=https://github.com/sasjs/core;
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&dir));
%mf_writefile(&dir/somefile.txt,l1=some content)
%mf_deletefile(&dir/package.json)
%mp_gitstatus(&dir,outds=work.gitstatus)
%mp_gitadd(&dir,inds=work.gitstatus)
@param [in] gitdir The directory containing the GIT repository
@param [in] inds= (work.mp_gitadd) The input dataset with the list of files
to stage. Will accept the output from mp_gitstatus(), else just use a table
with the following columns:
@li path $1024 - relative path to the file in the repo
@li staged $32 - whether the file is staged (TRUE or FALSE)
@li status $64 - either new, deleted, or modified
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
<h4> Related Files </h4>
@li mp_gitadd.test.sas
@li mp_gitstatus.sas
**/
%macro mp_gitadd(gitdir,inds=work.mp_gitadd,mdebug=0);
data _null_;
set &inds;
if STAGED ne "TRUE";
rc=git_index_add("&gitdir",cats(path),status);
if rc ne 0 or &mdebug=1 then put rc=;
run;
%mend mp_gitadd;

104
base/mp_gitlog.sas Normal file
View File

@@ -0,0 +1,104 @@
/**
@file
@brief Creates a dataset with the commit history of a local repository
@details Returns the commit history from a local repository. The name of the
branch is also returned.
More details here:
https://documentation.sas.com/doc/ko/pgmsascdc/v_033/lefunctionsref/n1qo5miyvry1nen111js203hlwrh.htm
Usage:
%let gitdir=%sysfunc(pathname(work))/core;
%let repo=https://github.com/sasjs/core;
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&dir));
%mp_gitlog(&gitdir,outds=work.mp_gitlog)
@param [in] gitdir The directory containing the GIT repository
@param [in] filter= (BRANCHONLY) To return only the commits for the current
branch, use BRANCHONLY (the default). Anything else will return the entire
commit history.
@param [out] outds= (work.mp_gitlog) The output dataset to create.
All vars are $128 except `message` which is $4000.
@li author returns the author who submitted the commit.
@li children_ids returns a list of the children commit IDs
@li committer returns the name of the committer.
@li committer_email returns the email of the committer.
@li email returns the email of the commit author.
@li id returns the commit ID of the commit object.
@li in_current_branch returns "TRUE" or "FALSE" to indicate if the commit is
in the current branch.
@li message returns the commit message.
@li parent_ids returns a list of the parent commit IDs.
@li stash returns "TRUE" or "FALSE" to indicate if the commit is a stash
commit.
@li time returns the time of the commit as numeric string
@li commit_time_num time of the commit as numeric SAS datetime
@li commit_time_str the commit_time_num variable cast as string
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
@param [in] nobs= (0) Set to an integer greater than 0 to restrict the number
of rows returned
<h4> SAS Macros </h4>
@li mf_getgitbranch.sas
<h4> Related Files </h4>
@li mp_gitadd.sas
@li mp_gitreleaseinfo.sas
@li mp_gitstatus.sas
**/
%macro mp_gitlog(gitdir,outds=work.mp_gitlog,mdebug=0,filter=BRANCHONLY,nobs=0);
%local varlist i var;
%let varlist=author children_ids committer committer_email email id
in_current_branch parent_ids stash time ;
data &outds;
LENGTH gitdir branch $ 1024 message $4000 &varlist $128 commit_time_num 8.
commit_time_str $32;
call missing (of _all_);
branch="%mf_getgitbranch(&gitdir)";
gitdir=symget('gitdir');
rc=git_status_free(trim(gitdir));
if rc=-1 then do;
put "The libgit2 library is unavailable and no Git operations can be used.";
put "See: https://stackoverflow.com/questions/74082874";
stop;
end;
else if rc=-2 then do;
put "The libgit2 library is available, but the status function failed.";
put "See the log for details.";
stop;
end;
entries=git_commit_log(trim(gitdir));
do n=1 to entries;
%do i=1 %to %sysfunc(countw(&varlist message));
%let var=%scan(&varlist message,&i,%str( ));
rc=git_commit_get(n,trim(gitdir),"&var",&var);
%end;
/* convert unix time to SAS time - https://4gl.uk/corelink0 */
/* Number of seconds between 01JAN1960 and 01JAN1970: 315619200 */
format commit_time_num datetime19.;
commit_time_num=sum(input(cats(time),best.),315619200);
commit_time_str=put(commit_time_num,datetime19.);
%if &mdebug=1 %then %do;
putlog (_all_)(=);
%end;
if "&filter"="BRANCHONLY" then do;
if cats(in_current_branch)='TRUE' then output;
end;
else output;
%if &nobs>0 %then %do;
if n ge &nobs then stop;
%end;
end;
rc=git_commit_free(trim(gitdir));
keep gitdir branch &varlist message time commit_time_num commit_time_str;
run;
%mend mp_gitlog;

View File

@@ -0,0 +1,74 @@
/**
@file
@brief Pulls latest release info from a GIT repository
@details Useful for grabbing the latest version number or other attributes
from a GIT server. Supported providers are GitLab and GitHub. Pull requests
are welcome if you'd like to see additional providers!
Note that each provider provides slightly different JSON output. Therefore
the macro simply extracts the JSON and assigns the libname (using the JSON
engine).
Example usage (eg, to grab latest release version from github):
%mp_gitreleaseinfo(GITHUB,sasjs/core,outlib=mylibref)
data _null_;
set mylibref.root;
putlog TAG_NAME=;
run;
@param [in] provider The GIT provider for the release info. Accepted values:
@li GITLAB
@li GITHUB - Tables include root, assets, author, alldata
@param [in] project The link to the repository. This has different formats
depending on the vendor:
@li GITHUB - org/repo, eg sasjs/core
@li GITLAB - project, eg 1343223
@param [in] server= (0) If your repo is self-hosted, then provide the domain
here. Otherwise it will default to the provider domain (eg gitlab.com).
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
@param [out] outlib= (GITREL) The JSON-engine libref to be created, which will
point at the returned JSON
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
<h4> Related Files </h4>
@li mp_gitreleaseinfo.test.sas
**/
%macro mp_gitreleaseinfo(provider,project,server=0,outlib=GITREL,mdebug=0);
%local url fref;
%let provider=%upcase(&provider);
%if &provider=GITHUB %then %do;
%if "&server"="0" %then %let server=https://api.github.com;
%let url=&server/repos/&project/releases/latest;
%end;
%else %if &provider=GITLAB %then %do;
%if "&server"="0" %then %let server=https://gitlab.com;
%let url=&server/api/v4/projects/&project/releases;
%end;
%let fref=%mf_getuniquefileref();
proc http method='GET' out=&fref url="&url";
%if &mdebug=1 %then %do;
debug level = 3;
%end;
run;
libname &outlib JSON fileref=&fref;
%if &mdebug=1 %then %do;
data _null_;
infile &fref;
input;
putlog _infile_;
run;
%end;
%mend mp_gitreleaseinfo;

67
base/mp_gitstatus.sas Normal file
View File

@@ -0,0 +1,67 @@
/**
@file
@brief Creates a dataset with the output from `GIT_STATUS()`
@details Uses `git_status()` to fetch the number of changed files, then
iterates with `git_status_get()`, inserting all attributes into an output
dataset.
Usage:
%let dir=%sysfunc(pathname(work))/core;
%let repo=https://github.com/sasjs/core;
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&dir));
%mf_writefile(&dir/somefile.txt,l1=some content)
%mf_deletefile(&dir/package.json)
%mp_gitstatus(&dir,outds=work.gitstatus)
More info on these functions is in this [helpful paper]
(https://www.sas.com/content/dam/SAS/support/en/sas-global-forum-proceedings/2019/3057-2019.pdf)
by Danny Zimmerman.
@param [in] gitdir The directory containing the GIT repository
@param [out] outds= (work.git_status) The output dataset to create. Vars:
@li gitdir $1024 - directory of repo
@li path $1024 - relative path to the file in the repo
@li staged $32 - whether the file is staged (TRUE or FALSE)
@li status $64 - either new, deleted, or modified
@li cnt - number of files
@li n - the "nth" file in the list from git_status()
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
<h4> Related Files </h4>
@li mp_gitstatus.test.sas
@li mp_gitadd.sas
**/
%macro mp_gitstatus(gitdir,outds=work.mp_gitstatus,mdebug=0);
data &outds;
LENGTH gitdir path $ 1024 STATUS $ 64 STAGED $ 32;
call missing (of _all_);
gitdir=symget('gitdir');
cnt=git_status(trim(gitdir));
if cnt=-1 then do;
put "The libgit2 library is unavailable and no Git operations can be used.";
put "See: https://stackoverflow.com/questions/74082874";
end;
else if cnt=-2 then do;
put "The libgit2 library is available, but the status function failed.";
put "See the log for details.";
end;
else do n=1 to cnt;
rc=GIT_STATUS_GET(n,gitdir,'PATH',path);
rc=GIT_STATUS_GET(n,gitdir,'STAGED',staged);
rc=GIT_STATUS_GET(n,gitdir,'STATUS',status);
output;
%if &mdebug=1 %then %do;
putlog (_all_)(=);
%end;
end;
rc=git_status_free(trim(gitdir));
drop rc cnt;
run;
%mend mp_gitstatus;

View File

@@ -5,7 +5,7 @@
create a hash for each directory also.
This makes use of the new `hashing_file()` and `hashing` functions, available
since 9.4m6. Interestingly, these can even be used in pure macro, eg:
since 9.4m6. Interestingly, those functions can be used in pure macro, eg:
%put %sysfunc(hashing_file(md5,/path/to/file.blob,0));
@@ -30,8 +30,9 @@
@li If a folder contains other folders, start from the bottom of the tree -
the folder hashes cascade upwards so you know immediately if there is a
change in a sub/sub directory
@li If the folder has no content (empty) then it is ignored. No hash created.
@li If a subfolder has no content (empty) then it is ignored. No hash created.
@li If the file is empty, it is also ignored / no hash created.
@li If the target directory (&inloc) is empty, &outds will also be empty
<h4> SAS Macros </h4>
@li mp_dirlist.sas
@@ -72,7 +73,7 @@
iftrue=%str(1=1)
)/*/STORE SOURCE*/;
%local curlevel tempds ;
%local curlevel tempds maxlevel;
%if not(%eval(%unquote(&iftrue))) %then %return;
@@ -108,6 +109,7 @@ proc sort data=&outds ;
by descending level directory file_path;
run;
%let maxlevel=0;
data _null_;
set &outds;
call symputx('maxlevel',level,'l');

View File

@@ -571,7 +571,24 @@ data _null_;
put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
put ' put ",""SYSCC"" : ""&syscc"" "; ';
put ' put ",""SYSENCODING"" : ""&sysencoding"" "; ';
put ' syserrortext=cats(''"'',tranwrd(symget(''syserrortext''),''"'',''\"''),''"''); ';
put ' syserrortext=cats(symget(''syserrortext'')); ';
put ' if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' syserrortext=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,syserrortext) ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
put ' put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" "; ';
@@ -584,7 +601,24 @@ data _null_;
put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
put ' put '',"SYSVLONG" : '' sysvlong; ';
put ' syswarningtext=cats(''"'',tranwrd(symget(''syswarningtext''),''"'',''\"''),''"''); ';
put ' syswarningtext=cats(symget(''syswarningtext'')); ';
put ' if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' syswarningtext=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,syswarningtext) ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; ';
put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
put ' length memsize $32; ';

View File

@@ -175,7 +175,24 @@
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSENCODING"" : ""&sysencoding"" ";
syserrortext=cats('"',tranwrd(symget('syserrortext'),'"','\"'),'"');
syserrortext=cats(symget('syserrortext'));
if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do;
syserrortext='"'!!trim(
prxchange('s/"/\\"/',-1, /* double quote */
prxchange('s/\x0A/\n/',-1, /* new line */
prxchange('s/\x0D/\r/',-1, /* carriage return */
prxchange('s/\x09/\\t/',-1, /* tab */
prxchange('s/\x00/\\u0000/',-1, /* NUL */
prxchange('s/\x0E/\\u000E/',-1, /* SS */
prxchange('s/\x0F/\\u000F/',-1, /* SF */
prxchange('s/\x01/\\u0001/',-1, /* SOH */
prxchange('s/\x02/\\u0002/',-1, /* STX */
prxchange('s/\x10/\\u0010/',-1, /* DLE */
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
prxchange('s/\\/\\\\/',-1,syserrortext)
)))))))))))))!!'"';
end;
put ',"SYSERRORTEXT" : ' syserrortext;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";
@@ -188,7 +205,24 @@
put ",""SYSUSERID"" : ""&sysuserid"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
syswarningtext=cats('"',tranwrd(symget('syswarningtext'),'"','\"'),'"');
syswarningtext=cats(symget('syswarningtext'));
if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do;
syswarningtext='"'!!trim(
prxchange('s/"/\\"/',-1, /* double quote */
prxchange('s/\x0A/\n/',-1, /* new line */
prxchange('s/\x0D/\r/',-1, /* carriage return */
prxchange('s/\x09/\\t/',-1, /* tab */
prxchange('s/\x00/\\u0000/',-1, /* NUL */
prxchange('s/\x0E/\\u000E/',-1, /* SS */
prxchange('s/\x0F/\\u000F/',-1, /* SF */
prxchange('s/\x01/\\u0001/',-1, /* SOH */
prxchange('s/\x02/\\u0002/',-1, /* STX */
prxchange('s/\x10/\\u0010/',-1, /* DLE */
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
prxchange('s/\\/\\\\/',-1,syswarningtext)
)))))))))))))!!'"';
end;
put ',"SYSWARNINGTEXT" : ' syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
length memsize $32;

View File

@@ -67,12 +67,16 @@
},
{
"name": "server",
"serverUrl": "https://sas.4gl.io",
"serverUrl": "https://sas9.4gl.io",
"serverType": "SASJS",
"httpsAgentOptions": {
"allowInsecureRequests": false
},
"appLoc": "/sasjs/core",
"deployConfig": {
"deployServicePack": true,
"deployScripts": []
},
"macroFolders": [
"server",
"tests/serveronly"
@@ -105,6 +109,16 @@
"deployServicePack": true
},
"contextName": "SAS Job Execution compute context"
},
{
"name": "sasjs9",
"serverUrl": "https://sas9.4gl.io",
"serverType": "SASJS",
"appLoc": "/Public/app/sasjs9",
"deployConfig": {
"deployServicePack": true,
"deployScripts": []
}
}
]
}

View File

@@ -0,0 +1,224 @@
/**
@file
@brief Deploy repo as a SAS PACKAGES module
@details After every release, this program is executed to update the SASPAC
repo with the latest macros (and same version number).
The program is first compiled using sasjs compile, then executed using
sasjs run.
Requires the server to have SSH keys.
<h4> SAS Macros </h4>
@li mp_gitadd.sas
@li mp_gitreleaseinfo.sas
@li mp_gitstatus.sas
**/
/* get package version */
%mp_gitreleaseinfo(GITHUB,sasjs/core,outlib=splib)
data _null_;
set splib.root;
call symputx('version',substr(TAG_NAME,2));
run;
/* clone the source repo */
%let dir = %sysfunc(pathname(work))/core;
%put source clone rc=%sysfunc(GITFN_CLONE(https://github.com/sasjs/core,&dir));
/*
clone the target repo.
If you have issues, see: https://stackoverflow.com/questions/74082874
*/
options dlcreatedir;
%let dirOut = %sysfunc(pathname(work))/package;
libname _ "&dirOut.";
%put tgt clone rc=%sysfunc(GITFN_CLONE(
git@github.com:SASPAC/sasjscore.git,
&dirOut,
git,
%str( ),
/home/sasjssrv/.ssh/id_ecdsa.pub,
/home/sasjssrv/.ssh/id_ecdsa
));
/*
Prepare Package Metadata
*/
data _null_;
infile CARDS4;
file "&dirOut./description.sas";
input;
if _infile_ =: 'Version:' then put "Version: &version.";
else put _infile_;
CARDS4;
Type: Package
Package: SASjsCore
Title: SAS Macros for Application Development
Version: $(PLACEHOLDER)
Author: Allan Bowe
Maintainer: 4GL Ltd
License: MIT
Encoding: UTF8
DESCRIPTION START:
The SASjs Macro Core library is a component of the SASjs framework, the
source for which is avaible here: https://github.com/sasjs
Macros are divided by:
* Macro Functions (prefix mf_)
* Macro Procedures (prefix mp_)
* Macros for Metadata (prefix mm_)
* Macros for SASjs Server (prefix ms_)
* Macros for Viya (prefix mv_)
DESCRIPTION END:
;;;;
run;
/*
Prepare Package License
*/
data _null_;
file "&dirOut./license.sas";
infile "&dir/LICENSE";
input;
put _infile_;
run;
/*
Extract Core files into MacroCore Package location
*/
data members(compress=char);
length dref dref2 $ 8 name name2 $ 32 path $ 2048;
rc = filename(dref, "&dir.");
put dref=;
did = dopen(dref);
if did then
do i = 1 to dnum(did);
name = dread(did, i);
if name in
("base" "ddl" "fcmp" "lua" "meta" "metax" "server" "viya" "xplatform")
then do;
rc = filename(dref2,catx("/", "&dir.", name));
put dref2= name;
did2 = dopen(dref2);
if did2 then
do j = 1 to dnum(did2);
name2 = dread(did2, j);
path = catx("/", "&dir.", name, name2);
if "sas" = scan(name2, -1, ".") then output;
end;
rc = dclose(did2);
rc = filename(dref2);
end;
end;
rc = dclose(did);
rc = filename(dref);
keep name name2 path;
run;
%let temp_options = %sysfunc(getoption(source)) %sysfunc(getoption(notes));
options nosource nonotes;
data _null_;
set members;
by name notsorted;
ord + first.name;
if first.name then
do;
call execute('libname _ '
!! quote(catx("/", "&dirOut.", put(ord, z3.)!!"_macros"))
!! ";"
);
put @1 "./" ord z3. "_macros/";
end;
put @10 name2;
call execute("
data _null_;
infile " !! quote(strip(path)) !! ";
file " !! quote(catx("/", "&dirOut.", put(ord, z3.)!!"_macros", name2)) !!";
input;
select;
when (2 = trigger) put _infile_;
when (_infile_ = '/**') do; put '/*** HELP START ***//**'; trigger+1; end;
when (_infile_ = '**/') do; put '**//*** HELP END ***/'; trigger+1; end;
otherwise put _infile_;
end;
run;");
run;
options &temp_options.;
/*
Generate SASjsCore Package
*/
%GeneratePackage(
filesLocation=&dirOut
)
/**
* apply new version in a github action
* 1. create folder
* 2. create template yaml
* 3. replace version number
*/
%mf_mkdir(&dirout/.github/workflows)
%let desc=Version &version of sasjs/core is now on SAS PACKAGES :ok_hand:;
data _null_;
file "&dirout/.github/workflows/release.yml";
put "name: SASjs Core Package Publish Tag";
put "on:";
put " push:";
put " branches:";
put " - main";
put "jobs:";
put " update:";
put " runs-on: ubuntu-latest";
put " steps:";
put " - uses: actions/checkout@master";
put " - name: Make Release";
put " uses: alice-biometrics/release-creator/@v1.0.5";
put " with:";
put " github_token: ${{ secrets.GH_TOKEN }}";
put " branch: main";
put " draft: false";
put " version: &version";
put " description: '&desc'";
run;
/**
* Add, Commit & Push!
*/
%mp_gitstatus(&dirout,outds=work.gitstatus,mdebug=1)
%mp_gitadd(&dirout,inds=work.gitstatus,mdebug=1)
data _null_;
rc=gitfn_commit("&dirout"
,"HEAD","&sysuserid","sasjs@core"
,"FEAT: Releasing &version"
);
put rc=;
rc=git_push(
"&dirout"
,"git"
,""
,"/home/sasjssrv/.ssh/id_ecdsa.pub"
,"/home/sasjssrv/.ssh/id_ecdsa"
);
run;

View File

@@ -560,7 +560,24 @@ data _null_;
put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
put ' put ",""SYSCC"" : ""&syscc"" "; ';
put ' put ",""SYSENCODING"" : ""&sysencoding"" "; ';
put ' syserrortext=cats(''"'',tranwrd(symget(''syserrortext''),''"'',''\"''),''"''); ';
put ' syserrortext=cats(symget(''syserrortext'')); ';
put ' if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' syserrortext=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,syserrortext) ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
put ' SYSHOSTINFOLONG=quote(trim(symget(''SYSHOSTINFOLONG''))); ';
put ' put '',"SYSHOSTINFOLONG" : '' SYSHOSTINFOLONG; ';
@@ -576,7 +593,24 @@ data _null_;
put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
put ' put '',"SYSVLONG" : '' sysvlong; ';
put ' syswarningtext=cats(''"'',tranwrd(symget(''syswarningtext''),''"'',''\"''),''"''); ';
put ' syswarningtext=cats(symget(''syswarningtext'')); ';
put ' if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' syswarningtext=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,syswarningtext) ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; ';
put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
put ' length memsize $32; ';

View File

@@ -162,7 +162,24 @@
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSENCODING"" : ""&sysencoding"" ";
syserrortext=cats('"',tranwrd(symget('syserrortext'),'"','\"'),'"');
syserrortext=cats(symget('syserrortext'));
if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do;
syserrortext='"'!!trim(
prxchange('s/"/\\"/',-1, /* double quote */
prxchange('s/\x0A/\n/',-1, /* new line */
prxchange('s/\x0D/\r/',-1, /* carriage return */
prxchange('s/\x09/\\t/',-1, /* tab */
prxchange('s/\x00/\\u0000/',-1, /* NUL */
prxchange('s/\x0E/\\u000E/',-1, /* SS */
prxchange('s/\x0F/\\u000F/',-1, /* SF */
prxchange('s/\x01/\\u0001/',-1, /* SOH */
prxchange('s/\x02/\\u0002/',-1, /* STX */
prxchange('s/\x10/\\u0010/',-1, /* DLE */
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
prxchange('s/\\/\\\\/',-1,syserrortext)
)))))))))))))!!'"';
end;
put ',"SYSERRORTEXT" : ' syserrortext;
SYSHOSTINFOLONG=quote(trim(symget('SYSHOSTINFOLONG')));
put ',"SYSHOSTINFOLONG" : ' SYSHOSTINFOLONG;
@@ -178,7 +195,24 @@
put ",""SYSUSERID"" : ""&sysuserid"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
syswarningtext=cats('"',tranwrd(symget('syswarningtext'),'"','\"'),'"');
syswarningtext=cats(symget('syswarningtext'));
if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do;
syswarningtext='"'!!trim(
prxchange('s/"/\\"/',-1, /* double quote */
prxchange('s/\x0A/\n/',-1, /* new line */
prxchange('s/\x0D/\r/',-1, /* carriage return */
prxchange('s/\x09/\\t/',-1, /* tab */
prxchange('s/\x00/\\u0000/',-1, /* NUL */
prxchange('s/\x0E/\\u000E/',-1, /* SS */
prxchange('s/\x0F/\\u000F/',-1, /* SF */
prxchange('s/\x01/\\u0001/',-1, /* SOH */
prxchange('s/\x02/\\u0002/',-1, /* STX */
prxchange('s/\x10/\\u0010/',-1, /* DLE */
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
prxchange('s/\\/\\\\/',-1,syswarningtext)
)))))))))))))!!'"';
end;
put ',"SYSWARNINGTEXT" : ' syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
length memsize $32;

View File

@@ -0,0 +1,20 @@
/**
@file
@brief Testing mf_getgitbranch.sas macro
<h4> SAS Macros </h4>
@li mf_getgitbranch.sas
@li mp_assert.sas
**/
/* grab core repo */
%let gitdir=%sysfunc(pathname(work))/core;
%let repo=https://github.com/sasjs/core;
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&gitdir));
%mp_assert(
iftrue=(%mf_getgitbranch(&gitdir)=main),
desc=Checking correct branch was obtained,
outds=work.test_results
)

View File

@@ -0,0 +1,40 @@
/**
@file
@brief Testing mf_readfile.sas macro
<h4> SAS Macros </h4>
@li mf_readfile.sas
@li mf_writefile.sas
@li mp_assert.sas
@li mp_assertscope.sas
**/
%let f=&sasjswork/myfile.txt;
%mf_writefile(&f,l1=some content,l2=more content)
data _null_;
infile "&f";
input;
putlog _infile_;
run;
%mp_assert(
iftrue=(&syscc=0),
desc=Check code ran without errors,
outds=work.test_results
)
/* test for scope leakage */
%global result;
%mp_assertscope(SNAPSHOT)
%put %mf_readfile(&f);
%mp_assertscope(COMPARE)
/* test result */
%mp_assert(
iftrue=(%mf_readfile(&f)=some content),
desc=Checking first line was ingested successfully,
outds=work.test_results
)

View File

@@ -0,0 +1,53 @@
/**
@file
@brief Testing mp_gitadd.sas macro
<h4> SAS Macros </h4>
@li mf_deletefile.sas
@li mf_writefile.sas
@li mp_gitadd.sas
@li mp_gitstatus.sas
@li mp_assert.sas
**/
/* clone the source repo */
%let dir = %sysfunc(pathname(work))/core;
%put source clone rc=%sysfunc(GITFN_CLONE(https://github.com/sasjs/core,&dir));
/* add a file */
%mf_writefile(&dir/somefile.txt,l1=some content)
/* change a file */
%mf_writefile(&dir/readme.md,l1=new readme)
/* delete a file */
%mf_deletefile(&dir/package.json)
/* Run git status */
%mp_gitstatus(&dir,outds=work.gitstatus)
%let test1=0;
proc sql noprint;
select count(*) into: test1 from work.gitstatus where staged='FALSE';
/* should be three unstaged changes now */
%mp_assert(
iftrue=(&test1=3),
desc=3 changes are ready to add,
outds=work.test_results
)
/* add them */
%mp_gitadd(&dir,inds=work.gitstatus,mdebug=&sasjs_mdebug)
/* check status */
%mp_gitstatus(&dir,outds=work.gitstatus2)
%let test2=0;
proc sql noprint;
select count(*) into: test2 from work.gitstatus2 where staged='TRUE';
/* should be three staged changes now */
%mp_assert(
iftrue=(&test2=3),
desc=3 changes were added,
outds=work.test_results
)

View File

@@ -0,0 +1,32 @@
/**
@file
@brief Testing mp_gitlog.sas macro
<h4> SAS Macros </h4>
@li mf_nobs.sas
@li mp_gitlog.sas
@li mp_assert.sas
@li mp_assertscope.sas
**/
/* grab core repo */
%let gitdir=%sysfunc(pathname(work))/core;
%let repo=https://github.com/sasjs/core;
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&gitdir));
%mp_assertscope(SNAPSHOT)
%mp_gitlog(&gitdir,outds=work.test1)
%mp_assertscope(COMPARE)
%mp_assert(
iftrue=(&syscc=0),
desc=Regular test works,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_nobs(work.test1)>1000),
desc=output has gt 1000 rows,
outds=work.test_results
)

View File

@@ -0,0 +1,30 @@
/**
@file
@brief Testing mp_gitreleaseinfo.sas macro
<h4> SAS Macros </h4>
@li mp_gitreleaseinfo.sas
@li mp_assert.sas
**/
%mp_gitreleaseinfo(github,sasjs/core,outlib=mylibref,mdebug=1)
%mp_assert(
iftrue=(&syscc=0),
desc=mp_gitreleaseinfo runs without errors,
outds=work.test_results
)
data _null_;
set mylibref.author;
putlog (_all_)(=);
call symputx('author',login);
run;
%mp_assert(
iftrue=(&author=sasjsbot),
desc=release info extracted successfully,
outds=work.test_results
)

View File

@@ -0,0 +1,39 @@
/**
@file
@brief Testing mp_gitstatus.sas macro
<h4> SAS Macros </h4>
@li mf_deletefile.sas
@li mf_writefile.sas
@li mp_gitstatus.sas
@li mp_assertdsobs.sas
**/
/* clone the source repo */
%let dir = %sysfunc(pathname(work))/core;
%put source clone rc=%sysfunc(GITFN_CLONE(https://github.com/sasjs/core,&dir));
%mp_gitstatus(&dir,outds=work.gitstatus)
%mp_assert(
iftrue=(&syscc=0),
desc=Initial mp_gitstatus runs without errors,
outds=work.test_results
)
/* should be empty as there are no changes yet */
%mp_assertdsobs(work.gitstatus,test=EMPTY)
/* add a file */
%mf_writefile(&dir/somefile.txt,l1=some content)
/* change a file */
%mf_writefile(&dir/readme.md,l1=new readme)
/* delete a file */
%mf_deletefile(&dir/package.json)
/* re-run git status */
%mp_gitstatus(&dir,outds=work.gitstatus)
/* should be three changes now */
%mp_assertdsobs(work.gitstatus,test=EQUALS 3)

View File

@@ -131,3 +131,19 @@ data _null_;
set work.hashes2;
put file_hash file_path;
run;
/* check that it works when the target directory is missing */
%mp_hashdirectory(&fpath/doesnotexist,outds=work.hashes3,maxdepth=MAX)
%mp_assert(
iftrue=(&syscc=0),
desc=No errors when directory is missing,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_nobs(work.hashes3)=0),
desc=no records created when directory is missing,
outds=work.test_results
)

60
viya/mfv_existsashdat.sas Normal file
View File

@@ -0,0 +1,60 @@
/**
@file mfv_existsashdat.sas
@brief Checks whether a CAS sashdat dataset exists in persistent storage.
@details Can be used in open code, eg as follows:
%if %mfv_existsashdat(libds=casuser.sometable) %then %put yes it does!;
The function uses `dosubl()` to run the `table.fileinfo` action, for the
specified library, filtering for `*.sashdat` tables. The results are stored
in a WORK table (&outprefix._&lib). If that table already exists, it is
queried instead, to avoid the dosubl() performance hit.
To force a rescan, just use a new `&outprefix` value, or delete the table(s)
before running the function.
@param libds library.dataset
@param outprefix= (work.mfv_existsashdat) Used to store the current HDATA
tables to improve subsequent query performance. This reference is a prefix
and is converted to `&prefix._{libref}`
@return output returns 1 or 0
@version 0.2
@author Mathieu Blauw
**/
%macro mfv_existsashdat(libds,outprefix=work.mfv_existsashdat
);
%local rc dsid name lib ds;
%let lib=%upcase(%scan(&libds,1,'.'));
%let ds=%upcase(%scan(&libds,-1,'.'));
/* if table does not exist, create it */
%if %sysfunc(exist(&outprefix._&lib)) ne 1 %then %do;
%let rc=%sysfunc(dosubl(%nrstr(
/* Read in table list (once per &lib per session) */
proc cas;
table.fileinfo result=source_list /caslib="&lib";
val=findtable(source_list);
saveresult val dataout=&outprefix._&lib;
quit;
/* Only keep name, without file extension */
data &outprefix._&lib;
set &outprefix._&lib(where=(Name like '%.sashdat') keep=Name);
Name=upcase(scan(Name,1,'.'));
run;
)));
%end;
/* Scan table for hdat existence */
%let dsid=%sysfunc(open(&outprefix._&lib(where=(name="&ds"))));
%syscall set(dsid);
%let rc = %sysfunc(fetch(&dsid));
%let rc = %sysfunc(close(&dsid));
/* Return result */
%if "%trim(&name)"="%trim(&ds)" %then 1;
%else 0;
%mend mfv_existsashdat;

View File

@@ -743,7 +743,24 @@ data _null_;
put ' put '',"SYS_JES_JOB_URI" : '' SYS_JES_JOB_URI ; ';
put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
put ' put ",""SYSCC"" : ""&syscc"" "; ';
put ' syserrortext=cats(''"'',tranwrd(symget(''syserrortext''),''"'',''\"''),''"''); ';
put ' syserrortext=cats(symget(''syserrortext'')); ';
put ' if findc(syserrortext,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' syserrortext=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,syserrortext) ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
put ' put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" "; ';
@@ -756,7 +773,24 @@ data _null_;
put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
put ' put '',"SYSVLONG" : '' sysvlong; ';
put ' syswarningtext=cats(''"'',tranwrd(symget(''syswarningtext''),''"'',''\"''),''"''); ';
put ' syswarningtext=cats(symget(''syswarningtext'')); ';
put ' if findc(syswarningtext,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' syswarningtext=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,syswarningtext) ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; ';
put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
put ' length memsize $32; ';

View File

@@ -204,7 +204,24 @@
put ',"SYS_JES_JOB_URI" : ' SYS_JES_JOB_URI ;
put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""SYSCC"" : ""&syscc"" ";
syserrortext=cats('"',tranwrd(symget('syserrortext'),'"','\"'),'"');
syserrortext=cats(symget('syserrortext'));
if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do;
syserrortext='"'!!trim(
prxchange('s/"/\\"/',-1, /* double quote */
prxchange('s/\x0A/\n/',-1, /* new line */
prxchange('s/\x0D/\r/',-1, /* carriage return */
prxchange('s/\x09/\\t/',-1, /* tab */
prxchange('s/\x00/\\u0000/',-1, /* NUL */
prxchange('s/\x0E/\\u000E/',-1, /* SS */
prxchange('s/\x0F/\\u000F/',-1, /* SF */
prxchange('s/\x01/\\u0001/',-1, /* SOH */
prxchange('s/\x02/\\u0002/',-1, /* STX */
prxchange('s/\x10/\\u0010/',-1, /* DLE */
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
prxchange('s/\\/\\\\/',-1,syserrortext)
)))))))))))))!!'"';
end;
put ',"SYSERRORTEXT" : ' syserrortext;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";
@@ -217,7 +234,24 @@
put ",""SYSUSERID"" : ""&sysuserid"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
syswarningtext=cats('"',tranwrd(symget('syswarningtext'),'"','\"'),'"');
syswarningtext=cats(symget('syswarningtext'));
if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do;
syswarningtext='"'!!trim(
prxchange('s/"/\\"/',-1, /* double quote */
prxchange('s/\x0A/\n/',-1, /* new line */
prxchange('s/\x0D/\r/',-1, /* carriage return */
prxchange('s/\x09/\\t/',-1, /* tab */
prxchange('s/\x00/\\u0000/',-1, /* NUL */
prxchange('s/\x0E/\\u000E/',-1, /* SS */
prxchange('s/\x0F/\\u000F/',-1, /* SF */
prxchange('s/\x01/\\u0001/',-1, /* SOH */
prxchange('s/\x02/\\u0002/',-1, /* STX */
prxchange('s/\x10/\\u0010/',-1, /* DLE */
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
prxchange('s/\\/\\\\/',-1,syswarningtext)
)))))))))))))!!'"';
end;
put ',"SYSWARNINGTEXT" : ' syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
length memsize $32;