diff --git a/.gitignore b/.gitignore index b911be2..0c20846 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ sasjsresults/ mc_* # ignore .env files as they can contain sasjs access tokens -*.env* \ No newline at end of file +*.env* + +~ diff --git a/all.sas b/all.sas index 064d032..398c61f 100644 --- a/all.sas +++ b/all.sas @@ -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 + +

SAS Macros

+ @li mf_readfile.sas + +

Related Macros

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

Related Macros

+ @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 @@ -8212,6 +8312,105 @@ data _null_; 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 + +

SAS Macros

+ @li mf_getgitbranch.sas + +

Related Files

+ @li mp_gitadd.sas + @li mp_gitreleaseinfo.sas + @li mp_gitstatus.sas + +**/ + +%macro mp_gitlog(gitdir,outds=work.mp_gitlog,mdebug=0,filter=BRANCHONLY); + +%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; + 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 @@ -8348,7 +8547,7 @@ data &outds; putlog (_all_)(=); %end; end; - rc=git_status_free(gitdir); + rc=git_status_free(trim(gitdir)); drop rc cnt; run; diff --git a/base/mf_getgitbranch.sas b/base/mf_getgitbranch.sas new file mode 100644 index 0000000..b46b8c3 --- /dev/null +++ b/base/mf_getgitbranch.sas @@ -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 + +

SAS Macros

+ @li mf_readfile.sas + +

Related Macros

+ @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; diff --git a/base/mf_readfile.sas b/base/mf_readfile.sas new file mode 100644 index 0000000..fa7fd97 --- /dev/null +++ b/base/mf_readfile.sas @@ -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 + +

Related Macros

+ @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 */ diff --git a/base/mp_gitlog.sas b/base/mp_gitlog.sas new file mode 100644 index 0000000..830fc18 --- /dev/null +++ b/base/mp_gitlog.sas @@ -0,0 +1,99 @@ +/** + @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 + +

SAS Macros

+ @li mf_getgitbranch.sas + +

Related Files

+ @li mp_gitadd.sas + @li mp_gitreleaseinfo.sas + @li mp_gitstatus.sas + +**/ + +%macro mp_gitlog(gitdir,outds=work.mp_gitlog,mdebug=0,filter=BRANCHONLY); + +%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; + end; + rc=git_commit_free(trim(gitdir)); + keep gitdir branch &varlist message time commit_time_num commit_time_str; +run; + +%mend mp_gitlog; diff --git a/base/mp_gitstatus.sas b/base/mp_gitstatus.sas index b27d52d..0428483 100644 --- a/base/mp_gitstatus.sas +++ b/base/mp_gitstatus.sas @@ -60,7 +60,7 @@ data &outds; putlog (_all_)(=); %end; end; - rc=git_status_free(gitdir); + rc=git_status_free(trim(gitdir)); drop rc cnt; run; diff --git a/tests/base/mf_getgitbranch.test.sas b/tests/base/mf_getgitbranch.test.sas new file mode 100644 index 0000000..d1225c9 --- /dev/null +++ b/tests/base/mf_getgitbranch.test.sas @@ -0,0 +1,20 @@ +/** + @file + @brief Testing mf_getgitbranch.sas macro + +

SAS Macros

+ @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 +) diff --git a/tests/base/mf_readfile.test.sas b/tests/base/mf_readfile.test.sas new file mode 100644 index 0000000..6a16908 --- /dev/null +++ b/tests/base/mf_readfile.test.sas @@ -0,0 +1,40 @@ +/** + @file + @brief Testing mf_readfile.sas macro + +

SAS Macros

+ @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 +) + diff --git a/tests/base/mp_gitlog.test.sas b/tests/base/mp_gitlog.test.sas new file mode 100644 index 0000000..ad6a40c --- /dev/null +++ b/tests/base/mp_gitlog.test.sas @@ -0,0 +1,32 @@ +/** + @file + @brief Testing mp_gitlog.sas macro + +

SAS Macros

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