From c1f1fcdebf9631419bb44a8e4dea8b4fa83cc554 Mon Sep 17 00:00:00 2001 From: allan Date: Mon, 9 Feb 2026 00:51:30 +0000 Subject: [PATCH 1/8] feat: new mx_createjob macro and associated test --- .gitpod.dockerfile | 6 -- .gitpod.yml | 27 ------ .npmignore | 1 - tests/x-platform/mx_createjob.test.sas | 27 ++++++ xplatform/mx_createjob.sas | 111 +++++++++++++++++++++++++ xplatform/mx_createwebservice.sas | 3 + 6 files changed, 141 insertions(+), 34 deletions(-) delete mode 100644 .gitpod.dockerfile delete mode 100644 .gitpod.yml create mode 100644 tests/x-platform/mx_createjob.test.sas create mode 100644 xplatform/mx_createjob.sas diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile deleted file mode 100644 index 6b680a1..0000000 --- a/.gitpod.dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM gitpod/workspace-full - -RUN sudo apt-get update \ - && sudo apt-get install -y doxygen \ - && sudo apt-get install -y graphviz \ - && sudo rm -rf /var/lib/apt/lists/* diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index b825a64..0000000 --- a/.gitpod.yml +++ /dev/null @@ -1,27 +0,0 @@ -tasks: - - init: npm install -g npm - - command: npm i - - command: npm i -g @sasjs/cli - -image: - file: .gitpod.dockerfile -vscode: - extensions: - - sasjs.sasjs-for-vscode - -github: - prebuilds: - # enable for the master/default branch (defaults to true) - master: true - # enable for all branches in this repo (defaults to false) - branches: false - # enable for pull requests coming from this repo (defaults to true) - pullRequests: true - # enable for pull requests coming from forks (defaults to false) - pullRequestsFromForks: true - # add a "Review in Gitpod" button as a comment to pull requests (defaults to true) - addComment: true - # add a "Review in Gitpod" button to pull requests (defaults to false) - addBadge: false - # add a label once the prebuild is ready to pull requests (defaults to false) - addLabel: prebuilt-in-gitpod diff --git a/.npmignore b/.npmignore index 8ea87e4..c9a7666 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,5 @@ all.sas build.py -.gitpod* tests/ sasjs/ .github/ diff --git a/tests/x-platform/mx_createjob.test.sas b/tests/x-platform/mx_createjob.test.sas new file mode 100644 index 0000000..187a329 --- /dev/null +++ b/tests/x-platform/mx_createjob.test.sas @@ -0,0 +1,27 @@ +/** + @file + @brief Testing mx_createjob.sas macro + + Be sure to run %let mcTestAppLoc=/Public/temp/macrocore; when + running in Studio + +

SAS Macros

+ @li mx_createjob.sas + @li mp_assert.sas + +**/ + +filename ft15f001 temp; +parmcards4; + data example1; + set sashelp.class; + run; + %put Job executed successfully; +;;;; +%mx_createjob(path=&mcTestAppLoc/jobs,name=testjob,replace=YES) + +%mp_assert( + iftrue=(&syscc=0), + desc=No errors after job creation, + outds=work.test_results +) diff --git a/xplatform/mx_createjob.sas b/xplatform/mx_createjob.sas new file mode 100644 index 0000000..d4b10a5 --- /dev/null +++ b/xplatform/mx_createjob.sas @@ -0,0 +1,111 @@ +/** + @file mx_createjob.sas + @brief Create a job in SAS 9, Viya or SASjs + @details Creates a Stored Process in SAS 9, a Job Execution Service in SAS + Viya, or a Stored Program on SASjs Server - depending on the executing + environment. + +Usage: + + %* compile macros ; + filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; + %inc mc; + + %* write some code; + filename ft15f001 temp; + parmcards4; + data example1; + set sashelp.class; + run; + ;;;; + + %* create the job; + %mx_createjob(path=/Public/app/jobs,name=myjob,replace=YES) + +

SAS Macros

+ @li mf_getplatform.sas + @li mm_createstp.sas + @li ms_createfile.sas + @li mv_createjob.sas + + @param [in,out] path= The full folder path where the job will be created + @param [in,out] name= Job name. Avoid spaces. + @param [in] desc= The description of the job (optional) + @param [in] precode= Space separated list of filerefs, pointing to the code + that needs to be attached to the beginning of the job (optional) + @param [in] code= (ft15f001) Space seperated fileref(s) of the actual code to + be added + @param [in] replace= (YES) Select YES to replace any existing job in that + location + @param [in] mDebug= (0) set to 1 to show debug messages in the log + + @author Allan Bowe + +

Related Macros

+ @li mx_createjob.test.sas + @li mx_createwebservice.sas + +**/ + +%macro mx_createjob(path=HOME + ,name=initJob + ,precode= + ,code=ft15f001 + ,desc=This job was created by the mx_createjob macro + ,replace=YES + ,mdebug=0 +)/*/STORE SOURCE*/; + +%if &syscc ge 4 %then %do; + %put syscc=&syscc - &sysmacroname will not execute in this state; + %return; +%end; + +/* combine precode and code into a single file */ +%local tempref x fref freflist; +%let tempref=%mf_getuniquefileref(); +%local work tmpfile; +%let work=%sysfunc(pathname(work)); +%let tmpfile=&tempref..sas; +filename &tempref "&work/&tmpfile"; +%let freflist=&precode &code ; +%do x=1 %to %sysfunc(countw(&freflist)); + %let fref=%scan(&freflist,&x); + %put &sysmacroname: adding &fref; + data _null_; + file &tempref lrecl=3000 termstr=crlf mod; + infile &fref lrecl=3000; + input; + put _infile_; + run; +%end; + +%local platform; %let platform=%mf_getplatform(); +%if &platform=SASVIYA %then %do; + %if "&path"="HOME" %then %let path=/Users/&sysuserid/My Folder; + %mv_createjob(path=&path + ,name=&name + ,code=&tempref + ,desc=&desc + ,replace=&replace + ) +%end; +%else %if &platform=SASJS %then %do; + %if "&path"="HOME" %then %let path=/Users/&_sasjs_username/My Folder; + %ms_createfile(&path/&name..sas + ,inref=&tempref + ,mdebug=&mdebug + ) +%end; +%else %do; + %if "&path"="HOME" %then %let path=/User Folders/&_METAPERSON/My Folder; + %mm_createstp(stpname=&name + ,filename=&tmpfile + ,directory=&work + ,tree=&path + ,stpdesc=&desc + ,mDebug=&mdebug + ) +%end; +filename &tempref clear; +%mend mx_createjob; diff --git a/xplatform/mx_createwebservice.sas b/xplatform/mx_createwebservice.sas index 8e41978..ff2e1ac 100644 --- a/xplatform/mx_createwebservice.sas +++ b/xplatform/mx_createwebservice.sas @@ -48,6 +48,9 @@ Usage: @author Allan Bowe +

Related Macros

+ @li mx_createjob.sas + **/ %macro mx_createwebservice(path=HOME From c32819df9f3b42bcd78569d4e127ab04c6204fa7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 9 Feb 2026 00:52:07 +0000 Subject: [PATCH 2/8] chore: updating all.sas --- all.sas | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/all.sas b/all.sas index ae29d40..dd0cf2d 100644 --- a/all.sas +++ b/all.sas @@ -30910,6 +30910,117 @@ endsub; %end; %mend mcf_string2file;/** + @file mx_createjob.sas + @brief Create a job in SAS 9, Viya or SASjs + @details Creates a Stored Process in SAS 9, a Job Execution Service in SAS + Viya, or a Stored Program on SASjs Server - depending on the executing + environment. + +Usage: + + %* compile macros ; + filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; + %inc mc; + + %* write some code; + filename ft15f001 temp; + parmcards4; + data example1; + set sashelp.class; + run; + ;;;; + + %* create the job; + %mx_createjob(path=/Public/app/jobs,name=myjob,replace=YES) + +

SAS Macros

+ @li mf_getplatform.sas + @li mm_createstp.sas + @li ms_createfile.sas + @li mv_createjob.sas + + @param [in,out] path= The full folder path where the job will be created + @param [in,out] name= Job name. Avoid spaces. + @param [in] desc= The description of the job (optional) + @param [in] precode= Space separated list of filerefs, pointing to the code + that needs to be attached to the beginning of the job (optional) + @param [in] code= (ft15f001) Space seperated fileref(s) of the actual code to + be added + @param [in] replace= (YES) Select YES to replace any existing job in that + location + @param [in] mDebug= (0) set to 1 to show debug messages in the log + + @author Allan Bowe + +

Related Macros

+ @li mx_createjob.test.sas + @li mx_createwebservice.sas + +**/ + +%macro mx_createjob(path=HOME + ,name=initJob + ,precode= + ,code=ft15f001 + ,desc=This job was created by the mx_createjob macro + ,replace=YES + ,mdebug=0 +)/*/STORE SOURCE*/; + +%if &syscc ge 4 %then %do; + %put syscc=&syscc - &sysmacroname will not execute in this state; + %return; +%end; + +/* combine precode and code into a single file */ +%local tempref x fref freflist; +%let tempref=%mf_getuniquefileref(); +%local work tmpfile; +%let work=%sysfunc(pathname(work)); +%let tmpfile=&tempref..sas; +filename &tempref "&work/&tmpfile"; +%let freflist=&precode &code ; +%do x=1 %to %sysfunc(countw(&freflist)); + %let fref=%scan(&freflist,&x); + %put &sysmacroname: adding &fref; + data _null_; + file &tempref lrecl=3000 termstr=crlf mod; + infile &fref lrecl=3000; + input; + put _infile_; + run; +%end; + +%local platform; %let platform=%mf_getplatform(); +%if &platform=SASVIYA %then %do; + %if "&path"="HOME" %then %let path=/Users/&sysuserid/My Folder; + %mv_createjob(path=&path + ,name=&name + ,code=&tempref + ,desc=&desc + ,replace=&replace + ) +%end; +%else %if &platform=SASJS %then %do; + %if "&path"="HOME" %then %let path=/Users/&_sasjs_username/My Folder; + %ms_createfile(&path/&name..sas + ,inref=&tempref + ,mdebug=&mdebug + ) +%end; +%else %do; + %if "&path"="HOME" %then %let path=/User Folders/&_METAPERSON/My Folder; + %mm_createstp(stpname=&name + ,filename=&tmpfile + ,directory=&work + ,tree=&path + ,stpdesc=&desc + ,mDebug=&mdebug + ) +%end; +filename &tempref clear; +%mend mx_createjob; +/** @file mx_createwebservice.sas @brief Create a web service in SAS 9, Viya or SASjs @details Creates a SASJS ready Stored Process in SAS 9, a Job Execution @@ -30959,6 +31070,9 @@ Usage: @author Allan Bowe +

Related Macros

+ @li mx_createjob.sas + **/ %macro mx_createwebservice(path=HOME From 835369381c62ba4ae37ddc47bb8061cd0bc96482 Mon Sep 17 00:00:00 2001 From: allan Date: Mon, 9 Feb 2026 01:28:43 +0000 Subject: [PATCH 3/8] chore: more tests --- tests/x-platform/mx_createjob.test.sas | 277 ++++++++++++++++++++++++- 1 file changed, 275 insertions(+), 2 deletions(-) diff --git a/tests/x-platform/mx_createjob.test.sas b/tests/x-platform/mx_createjob.test.sas index 187a329..cb427ba 100644 --- a/tests/x-platform/mx_createjob.test.sas +++ b/tests/x-platform/mx_createjob.test.sas @@ -8,9 +8,13 @@

SAS Macros

@li mx_createjob.sas @li mp_assert.sas + @li mf_getuniquefileref.sas **/ +/** + * Test 1 - Basic job creation with default parameters + */ filename ft15f001 temp; parmcards4; data example1; @@ -18,10 +22,279 @@ parmcards4; run; %put Job executed successfully; ;;;; -%mx_createjob(path=&mcTestAppLoc/jobs,name=testjob,replace=YES) +%mx_createjob(path=&mcTestAppLoc/jobs,name=testjob1,replace=YES) %mp_assert( iftrue=(&syscc=0), - desc=No errors after job creation, + desc=Test 1: No errors after basic job creation, + outds=work.test_results +) + +/** + * Test 2 - Job creation with custom description + */ +filename ft15f001 temp; +parmcards4; + data example2; + set sashelp.cars; + run; +;;;; +%mx_createjob( + path=&mcTestAppLoc/jobs, + name=testjob2, + desc=Custom job description for testing, + replace=YES +) + +%mp_assert( + iftrue=(&syscc=0), + desc=Test 2: Job created with custom description, + outds=work.test_results +) + +/** + * Test 3 - Job creation with precode + */ +filename precode1 temp; +data _null_; + file precode1; + put '%let testvar=PreCodeValue;'; + put '%put &=testvar;'; +run; + +filename ft15f001 temp; +parmcards4; + data example3; + set sashelp.class; + precode_var="&testvar"; + run; +;;;; +%mx_createjob( + path=&mcTestAppLoc/jobs, + name=testjob3, + precode=precode1, + replace=YES +) + +%mp_assert( + iftrue=(&syscc=0), + desc=Test 3: Job created with precode parameter, + outds=work.test_results +) + +filename precode1 clear; + +/** + * Test 4 - Job creation with multiple code filerefs + */ +%let code1=%mf_getuniquefileref(); +%let code2=%mf_getuniquefileref(); + +filename &code1 temp; +data _null_; + file &code1; + put 'data work.part1;'; + put ' set sashelp.class(obs=5);'; + put 'run;'; +run; + +filename &code2 temp; +data _null_; + file &code2; + put 'data work.part2;'; + put ' set sashelp.class(firstobs=6);'; + put 'run;'; +run; + +%mx_createjob( + path=&mcTestAppLoc/jobs, + name=testjob4, + code=&code1 &code2, + replace=YES +) + +%mp_assert( + iftrue=(&syscc=0), + desc=Test 4: Job created with multiple code filerefs, + outds=work.test_results +) + +filename &code1 clear; +filename &code2 clear; + +/** + * Test 5 - Job creation with both precode and multiple code files + */ +%let pre1=%mf_getuniquefileref(); +%let pre2=%mf_getuniquefileref(); +%let main1=%mf_getuniquefileref(); + +filename &pre1 temp; +data _null_; + file &pre1; + put '%let globalvar1=Value1;'; +run; + +filename &pre2 temp; +data _null_; + file &pre2; + put '%let globalvar2=Value2;'; +run; + +filename &main1 temp; +data _null_; + file &main1; + put 'data work.combined;'; + put ' var1="&globalvar1";'; + put ' var2="&globalvar2";'; + put ' output;'; + put 'run;'; +run; + +%mx_createjob( + path=&mcTestAppLoc/jobs, + name=testjob5, + precode=&pre1 &pre2, + code=&main1, + desc=Job with multiple precode and code files, + replace=YES +) + +%mp_assert( + iftrue=(&syscc=0), + desc=Test 5: Job created with multiple precode and code files, + outds=work.test_results +) + +filename &pre1 clear; +filename &pre2 clear; +filename &main1 clear; + +/** + * Test 6 - Job creation with special characters in code + */ +filename ft15f001 temp; +parmcards4; + data example6; + length text $200; + text='Special chars: & % $ # @ !'; + output; + text="Quotes: 'single' and ""double"""; + output; + run; + %put Test with special characters; +;;;; +%mx_createjob( + path=&mcTestAppLoc/jobs, + name=testjob6, + desc=Job with special characters in code, + replace=YES +) + +%mp_assert( + iftrue=(&syscc=0), + desc=Test 6: Job created with special characters in code, + outds=work.test_results +) + +/** + * Test 7 - Job creation with macro code + */ +filename ft15f001 temp; +parmcards4; + %macro testmacro(); + data example7; + set sashelp.class; + where age > 12; + run; + %mend testmacro; + + %testmacro() +;;;; +%mx_createjob( + path=&mcTestAppLoc/jobs, + name=testjob7, + desc=Job containing macro definitions, + replace=YES +) + +%mp_assert( + iftrue=(&syscc=0), + desc=Test 7: Job created with macro code, + outds=work.test_results +) + +/** + * Test 8 - Job creation with empty code (edge case) + */ +filename ft15f001 temp; +data _null_; + file ft15f001; + put '/* Empty job for testing */'; +run; + +%mx_createjob( + path=&mcTestAppLoc/jobs, + name=testjob8, + desc=Job with minimal code, + replace=YES +) + +%mp_assert( + iftrue=(&syscc=0), + desc=Test 8: Job created with minimal code, + outds=work.test_results +) + +/** + * Test 9 - Job creation with long code block + */ +filename ft15f001 temp; +data _null_; + file ft15f001; + put 'data work.longtest;'; + do i=1 to 50; + put ' var' i +(-1) '=' i ';'; + end; + put ' output;'; + put 'run;'; +run; + +%mx_createjob( + path=&mcTestAppLoc/jobs, + name=testjob9, + desc=Job with many variables, + replace=YES +) + +%mp_assert( + iftrue=(&syscc=0), + desc=Test 9: Job created with long code block, + outds=work.test_results +) + +/** + * Test 10 - Replace existing job (replace=YES) + */ +filename ft15f001 temp; +parmcards4; + data example10_v1; + set sashelp.class; + run; +;;;; +%mx_createjob(path=&mcTestAppLoc/jobs,name=testjob10,replace=YES) + +/* Now replace it */ +filename ft15f001 temp; +parmcards4; + data example10_v2; + set sashelp.cars; + run; +;;;; +%mx_createjob(path=&mcTestAppLoc/jobs,name=testjob10,replace=YES) + +%mp_assert( + iftrue=(&syscc=0), + desc=Test 10: Job replaced successfully with replace=YES, outds=work.test_results ) From dd5e4edc800bb91021f9e81f19406734293cbf02 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 9 Feb 2026 01:29:50 +0000 Subject: [PATCH 4/8] chore: updating all.sas From 3bb902b74ead2ab7f9258bf1ce0fbe9fcc611c1c Mon Sep 17 00:00:00 2001 From: allan Date: Mon, 9 Feb 2026 01:35:20 +0000 Subject: [PATCH 5/8] chore: adding scope test --- tests/x-platform/mx_createjob.test.sas | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/x-platform/mx_createjob.test.sas b/tests/x-platform/mx_createjob.test.sas index cb427ba..5c958d8 100644 --- a/tests/x-platform/mx_createjob.test.sas +++ b/tests/x-platform/mx_createjob.test.sas @@ -9,11 +9,13 @@ @li mx_createjob.sas @li mp_assert.sas @li mf_getuniquefileref.sas + @li mp_assertscope.sas **/ /** * Test 1 - Basic job creation with default parameters + * Also checking for scope leakage */ filename ft15f001 temp; parmcards4; @@ -22,7 +24,9 @@ parmcards4; run; %put Job executed successfully; ;;;; +%mp_assertscope(SNAPSHOT) %mx_createjob(path=&mcTestAppLoc/jobs,name=testjob1,replace=YES) +%mp_assertscope(COMPARE) %mp_assert( iftrue=(&syscc=0), From 87ce565321fa8c60c4ec2e8a324a470ce9bd0ffa Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 9 Feb 2026 01:36:38 +0000 Subject: [PATCH 6/8] chore: updating all.sas From 9e36e82ff2103c6d5a3acfb5b5921157ae6842ee Mon Sep 17 00:00:00 2001 From: allan Date: Mon, 9 Feb 2026 17:54:07 +0000 Subject: [PATCH 7/8] fix: uninitialied warnings in strict mode for mv_deletejes --- viya/mv_deletejes.sas | 1 + 1 file changed, 1 insertion(+) diff --git a/viya/mv_deletejes.sas b/viya/mv_deletejes.sas index 27532a4..994ce93 100644 --- a/viya/mv_deletejes.sas +++ b/viya/mv_deletejes.sas @@ -118,6 +118,7 @@ libname &libref1a JSON fileref=&fname1a; /* %put Getting object uri from &libref1a..items; */ data _null_; length contenttype name $1000; + call missing(of _all_); set &libref1a..items; if contenttype='jobDefinition' and upcase(name)="%upcase(&name)" then do; call symputx('uri',cats("&base_uri",uri),'l'); From 6721e73ecd8faf8c056445429222c2e252999b21 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 9 Feb 2026 17:55:09 +0000 Subject: [PATCH 8/8] chore: updating all.sas --- all.sas | 1 + 1 file changed, 1 insertion(+) diff --git a/all.sas b/all.sas index dd0cf2d..f008c4e 100644 --- a/all.sas +++ b/all.sas @@ -26428,6 +26428,7 @@ libname &libref1a JSON fileref=&fname1a; /* %put Getting object uri from &libref1a..items; */ data _null_; length contenttype name $1000; + call missing(of _all_); set &libref1a..items; if contenttype='jobDefinition' and upcase(name)="%upcase(&name)" then do; call symputx('uri',cats("&base_uri",uri),'l');