diff --git a/all.sas b/all.sas
index cbfafcf..0fc88f9 100644
--- a/all.sas
+++ b/all.sas
@@ -960,15 +960,17 @@ or %index(&pgm,/tests/testteardown)
be 8 characters, so a 7 letter prefix would mean `maxtries` should be 10.
if using zero (0) as the prefix, a native assignment is used.
@param [in] maxtries= (1000) the last part of the libref. Must be an integer.
+ @param [in] lrecl= (32767) Provide a default lrecl with which to initialise
+ the generated fileref.
@version 9.2
@author Allan Bowe
**/
-%macro mf_getuniquefileref(prefix=_,maxtries=1000);
+%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);
%local rc fname;
%if &prefix=0 %then %do;
- %let rc=%sysfunc(filename(fname,,temp));
+ %let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));
%if &rc %then %put %sysfunc(sysmsg());
&fname
%end;
@@ -979,7 +981,7 @@ or %index(&pgm,/tests/testteardown)
%do x=0 %to &maxtries;
%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);
%if %sysfunc(fileref(&fname)) > 0 %then %do;
- %let rc=%sysfunc(filename(fname,,temp));
+ %let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));
%if &rc %then %put %sysfunc(sysmsg());
&fname
%return;
@@ -1604,8 +1606,11 @@ Usage:
%macro mf_isint(arg
)/*/STORE SOURCE*/;
- /* remove minus sign if exists */
+ /* blank val is not an integer */
+ %if "&arg"="" %then %do;0%return;%end;
+
+ /* remove minus sign if exists */
%local val;
%if "%substr(%str(&arg),1,1)"="-" %then %let val=%substr(%str(&arg),2);
%else %let val=&arg;
@@ -3441,6 +3446,200 @@ run;
filename &outref clear;
%end;
%mend mp_binarycopy;/**
+ @file
+ @brief Splits a file of ANY SIZE by reference to a search string.
+ @details Provide a fileref and a search string to chop off part of a file.
+
+ Works by reading in the file byte by byte, then marking the beginning and end
+ of each matched string, before finally doing the chop.
+
+ Choose whether to keep the FIRST or the LAST section of the file. Optionally,
+ use an OFFSET to fix the precise chop point.
+
+ Usage:
+
+ %let src="%sysfunc(pathname(work))/file.txt";
+ %let str=Chop here!;
+ %let out1="%sysfunc(pathname(work))/file1.txt";
+ %let out2="%sysfunc(pathname(work))/file2.txt";
+ %let out3="%sysfunc(pathname(work))/file3.txt";
+ %let out4="%sysfunc(pathname(work))/file4.txt";
+
+ data _null_;
+ file &src;
+ put "startsection&str.endsection";
+ run;
+
+ %mp_chop(&src, matchvar=str, keep=FIRST, outfile=&out1)
+ %mp_chop(&src, matchvar=str, keep=LAST, outfile=&out2)
+ %mp_chop(&src, matchvar=str, keep=FIRST, matchpoint=END, outfile=&out3)
+ %mp_chop(&src, matchvar=str, keep=LAST, matchpoint=END, outfile=&out4)
+
+ filename results (&out1 &out2 &out3 &out4);
+ data _null_;
+ infile results;
+ input;
+ list;
+ run;
+
+ Results:
+ @li `startsection`
+ @li `Chop here!endsection`
+ @li `startsectionChop here!`
+ @li `endsection`
+
+ For more examples, see mp_chop.test.sas
+
+ @param [in] infile The QUOTED path to the file on which to perform the chop
+ @param [in] matchvar= Macro variable NAME containing the string to split by
+ @param [in] matchpoint= (START) Valid values:
+ @li START - chop at the beginning of the string in `matchvar`.
+ @li END - chop at the end of the string in `matchvar`.
+ @param [in] offset= (0) An adjustment to the precise chop location, by
+ by reference to the `matchpoint`. Should be a positive or negative integer.
+ @param [in] keep= (FIRST) Valid values:
+ @li FIRST - keep the section of the file before the chop
+ @li LAST - keep the section of the file after the chop
+ @param [in] mdebug= (0) Set to 1 to provide macro debugging
+ @param outfile= (0) Optional QUOTED path to the adjusted output file (avoids
+ overwriting the first file).
+
+
SAS Macros
+ @li mf_getuniquefileref.sas
+ @li mf_getuniquename.sas
+
+ Related Macros
+ @li mp_abort.sas
+ @li mp_gsubfile.sas
+ @li mp_replace.sas
+ @li mp_chop.test.sas
+
+ @version 9.4
+ @author Allan Bowe
+
+**/
+
+%macro mp_chop(infile,
+ matchvar=,
+ matchpoint=START,
+ keep=FIRST,
+ offset=0,
+ mdebug=0,
+ outfile=0
+)/*/STORE SOURCE*/;
+
+%local fref0 dttm ds1 outref;
+%let fref0=%mf_getuniquefileref();
+%let ds1=%mf_getuniquename(prefix=allchars);
+%let ds2=%mf_getuniquename(prefix=startmark);
+
+%if &outfile=0 %then %let outfile=&infile;
+
+%mp_abort(iftrue= (%length(%superq(&matchvar))=0)
+ ,mac=mp_chop.sas
+ ,msg=%str(&matchvar is an empty variable)
+)
+
+/* START */
+%let dttm=%sysfunc(datetime());
+
+filename &fref0 &infile lrecl=1 recfm=n;
+
+/* create dataset with one char per row */
+data &ds1;
+ infile &fref0;
+ input sourcechar $char1. @@;
+ format sourcechar hex2.;
+run;
+
+/* get start & stop position of first matchvar string (one row, two vars) */
+data &ds2;
+ /* set find string to length in bytes to cover trailing spaces */
+ length string $ %length(%superq(&matchvar));
+ string =symget("&matchvar");
+ drop string;
+
+ firstchar=char(string,1);
+ findlen=lengthm(string); /* <- for trailing bytes */
+
+ do _N_=1 to nobs;
+ set &ds1 nobs=nobs point=_N_;
+ if sourcechar=firstchar then do;
+ pos=1;
+ s=0;
+ do point=_N_ to min(_N_ + findlen -1,nobs);
+ set &ds1 point=point;
+ if sourcechar=char(string, pos) then s + 1;
+ else goto _leave_;
+ pos+1;
+ end;
+ _leave_:
+ if s=findlen then do;
+ START =_N_;
+ _N_ =_N_+ s - 1;
+ STOP =_N_;
+ output;
+ /* matched! */
+ stop;
+ end;
+ end;
+ end;
+ stop;
+ keep START STOP;
+run;
+
+%local split;
+%let split=0;
+data _null_;
+ set &ds2;
+ if "&matchpoint"='START' then do;
+ if "&keep"='FIRST' then mp=start;
+ else if "&keep"='LAST' then mp=start-1;
+ end;
+ else if "&matchpoint"='END' then do;
+ if "&keep"='FIRST' then mp=stop+1;
+ else if "&keep"='LAST' then mp=stop;
+ end;
+ split=mp+&offset;
+ call symputx('split',split,'l');
+%if &mdebug=1 %then %do;
+ put (_all_)(=);
+ %put &=offset;
+%end;
+run;
+%if &split=0 %then %do;
+ %put &sysmacroname: No match found in &infile for string %superq(&matchvar);
+ %return;
+%end;
+
+data _null_;
+ file &outfile recfm=n;
+ set &ds1;
+%if &keep=FIRST %then %do;
+ if _n_ ge &split then stop;
+%end;
+%else %do;
+ if _n_ gt &split;
+%end;
+ put sourcechar char1.;
+run;
+
+%if &mdebug=0 %then %do;
+ filename &fref0 clear;
+%end;
+%else %do;
+ data _null_;
+ infile &outfile lrecl=32767;
+ input;
+ list;
+ if _n_>50 then stop;
+ run;
+%end;
+/* END */
+%put &sysmacroname took %sysevalf(%sysfunc(datetime())-&dttm) seconds to run;
+
+%mend mp_chop;
+/**
@file mp_cleancsv.sas
@brief Fixes embedded cr / lf / crlf in CSV
@details CSVs will sometimes contain lf or crlf within quotes (eg when
@@ -3841,6 +4040,7 @@ Usage:
SAS Macros
@li mf_getplatform.sas
@li mm_createwebservice.sas
+ @li ms_createwebservice.sas
@li mv_createwebservice.sas
@param [in,out] path= The full folder path where the service will be created
@@ -3852,7 +4052,7 @@ Usage:
be added
@param [in] replace= (YES) Select YES to replace any existing service in that
location
-
+ @param [in] mDebug= (0) set to 1 to show debug messages in the log
@version 9.2
@author Allan Bowe
@@ -3865,6 +4065,7 @@ Usage:
,code=ft15f001
,desc=This service was created by the mp_createwebservice macro
,replace=YES
+ ,mdebug=0
)/*/STORE SOURCE*/;
%if &syscc ge 4 %then %do;
@@ -3883,8 +4084,17 @@ Usage:
,replace=&replace
)
%end;
+%else %if &platform=SASJS %then %do;
+ %if "&path"="HOME" %then %let path=/Users/&_sasjs_username/My Folder;
+ %ms_createwebservice(path=&path
+ ,name=&name
+ ,code=&code
+ ,precode=&precode
+ ,mdebug=&mdebug
+ )
+%end;
%else %do;
- %if "&path"="HOME" %then %let path=/User Folders/&sysuserid/My Folder;
+ %if "&path"="HOME" %then %let path=/User Folders/&_METAPERSON/My Folder;
%mm_createwebservice(path=&path
,name=&name
,code=&code
@@ -7635,7 +7845,7 @@ create table &outds as
outfile=0
)/*/STORE SOURCE*/;
- %if "%substr(&sysver,1,4)"="V.04" %then %do;
+ %if "%substr(&sysver.XX,1,4)"="V.04" %then %do;
%put %str(ERR)OR: Viya 4 does not support the IO library in lua;
%return;
%end;
@@ -8312,7 +8522,7 @@ options
%if &action=OPEN %then %do;
options nobomfile;
- data _null_;file &jref encoding='utf-8' ;
+ data _null_;file &jref encoding='utf-8' lrecl=200;
put '{"PROCESSED_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '"';
run;
%end;
@@ -9742,7 +9952,7 @@ insert into &outds select distinct * from &append_ds;
@param infile The QUOTED path to the file on which to perform the substitution
@param findvar= Macro variable NAME containing the string to search for
@param replacevar= Macro variable NAME containing the replacement string
- @param outfile= (0) Optional QUOTED path to an the adjusted output file (to
+ @param outfile= (0) Optional QUOTED path to the adjusted output file (to
avoid overwriting the first file).
SAS Macros
@@ -9750,7 +9960,9 @@ insert into &outds select distinct * from &append_ds;
@li mf_getuniquename.sas
Related Macros
- @li mp_gsubfile.test.sas
+ @li mp_chop.sas
+ @li mp_gsubfile.sas
+ @li mp_replace.test.sas
@version 9.4
@author Bartosz Jabłoński
@@ -11844,7 +12056,9 @@ libname &lib clear;
@li mf_getuniquename.sas
@li mp_abort.sas
@li mp_binarycopy.sas
+ @li mp_chop.sas
@li mp_ds2csv.sas
+ @li ms_testservice.sas
@li mv_getjobresult.sas
@li mv_jobflow.sas
@@ -11867,7 +12081,7 @@ libname &lib clear;
viyaresult=WEBOUT_JSON,
viyacontext=SAS Job Execution compute context
)/*/STORE SOURCE*/;
-%local dbg pcnt fref1 webref i webcount var platform;
+%local dbg pcnt fref1 fref2 webref webrefpath i webcount var platform;
%if &mdebug=1 %then %do;
%put &sysmacroname entry vars:;
%put _local_;
@@ -11908,10 +12122,14 @@ libname &lib clear;
%end;
%end;
-
-%let fref1=%mf_getuniquefileref();
-%let webref=%mf_getuniquefileref();
%let platform=%mf_getplatform();
+%let fref1=%mf_getuniquefileref();
+%let fref2=%mf_getuniquefileref();
+%let webref=%mf_getuniquefileref();
+%let webrefpath=%sysfunc(pathname(work))/%mf_getuniquename();
+/* mp_chop requires a physical path as input */
+filename &webref "&webrefpath";
+
%if &platform=SASMETA %then %do;
/* parse the input files */
@@ -12066,6 +12284,19 @@ libname &lib clear;
mdebug=&mdebug
)
+%end;
+%else %if &platform=SASJS %then %do;
+
+ %ms_testservice(&program
+ ,inputfiles=&inputfiles
+ ,inputdatasets=&inputdatasets
+ ,inputparams=&inputparams
+ ,debug=&debug
+ ,mdebug=&mdebug
+ ,outlib=&outlib
+ ,outref=&outref
+ )
+
%end;
%else %do;
%put %str(ERR)OR: Unrecognised platform: &platform;
@@ -12073,6 +12304,8 @@ libname &lib clear;
%if &mdebug=0 %then %do;
filename &webref clear;
+ filename &fref1 clear;
+ filename &fref2 clear;
%end;
%else %do;
%put &sysmacroname exit vars:;
@@ -14841,7 +15074,7 @@ data _null_;
put ' ';
put '%if &action=OPEN %then %do; ';
put ' options nobomfile; ';
- put ' data _null_;file &jref encoding=''utf-8'' ; ';
+ put ' data _null_;file &jref encoding=''utf-8'' lrecl=200; ';
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
put ' run; ';
put '%end; ';
@@ -15052,6 +15285,25 @@ data _null_;
put ' run; ';
put '%end; ';
put '%mend mp_jsonout; ';
+ put ' ';
+ put '%macro mf_getuser(type=META ';
+ put ')/*/STORE SOURCE*/; ';
+ put ' %local user metavar; ';
+ put ' %if &type=OS %then %let metavar=_secureusername; ';
+ put ' %else %let metavar=_metaperson; ';
+ put ' ';
+ put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
+ put ' %else %if %symexist(&metavar) %then %do; ';
+ put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
+ put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
+ put ' /* but be sure to quote in case of usernames with commas */ ';
+ put ' %else %let user=%unquote(%scan(%quote(&&&metavar),1,@)); ';
+ put ' %end; ';
+ put ' %else %let user=&sysuserid; ';
+ put ' ';
+ put ' %quote(&user) ';
+ put ' ';
+ put '%mend mf_getuser; ';
put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL ';
put ' ,showmeta=NO ';
put '); ';
@@ -15193,25 +15445,6 @@ data _null_;
put '%end; ';
put ' ';
put '%mend mm_webout; ';
- put ' ';
- put '%macro mf_getuser(type=META ';
- put ')/*/STORE SOURCE*/; ';
- put ' %local user metavar; ';
- put ' %if &type=OS %then %let metavar=_secureusername; ';
- put ' %else %let metavar=_metaperson; ';
- put ' ';
- put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
- put ' %else %if %symexist(&metavar) %then %do; ';
- put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
- put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
- put ' /* but be sure to quote in case of usernames with commas */ ';
- put ' %else %let user=%unquote(%scan(%quote(&&&metavar),1,@)); ';
- put ' %end; ';
- put ' %else %let user=&sysuserid; ';
- put ' ';
- put ' %quote(&user) ';
- put ' ';
- put '%mend mf_getuser; ';
/* WEBOUT END */
put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO);';
put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing';
@@ -18877,7 +19110,7 @@ run;
Usage:
- %mfs_httpheader(Content-type,application/csv)
+ %mfs_httpheader(Content-Type,application/csv)
@param [in] header_name Name of the http header to set
@param [in] header_value Value of the http header to set
@@ -18893,6 +19126,7 @@ run;
%macro mfs_httpheader(header_name
,header_value
)/*/STORE SOURCE*/;
+%global sasjs_stpsrv_header_loc;
%local fref fid i;
%if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc)) ne 0 %then %do;
@@ -18938,6 +19172,7 @@ run;
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mp_abort.sas
+ @li ms_deletefile.sas
**/
@@ -18946,6 +19181,9 @@ run;
,mdebug=0
);
+/* first, delete in case it exists */
+%ms_deletefile(&driveloc,mdebug=&mdebug)
+
%local fname0 fname1 fname2 boundary fname statcd msg optval;
%let fname0=%mf_getuniquefileref();
%let fname1=%mf_getuniquefileref();
@@ -18957,8 +19195,8 @@ run;
options nobomfile;
data _null_;
- file &fname0 termstr=crlf;
- infile &inref end=eof;
+ file &fname0 termstr=crlf lrecl=32767;
+ infile &inref end=eof lrecl=32767;
if _n_ = 1 then do;
put "--&boundary.";
put 'Content-Disposition: form-data; name="filePath"';
@@ -18987,11 +19225,11 @@ run;
%if &mdebug=1 %then %do;
data _null_;
- infile &fname0;
+ infile &fname0 lrecl=32767;
input;
put _infile_;
data _null_;
- infile &fname1;
+ infile &fname1 lrecl=32767;
input;
put _infile_;
run;
@@ -19167,6 +19405,538 @@ options &optval;
%end;
%mend ms_createuser;
+/**
+ @file ms_createwebservice.sas
+ @brief Create a Web-Ready Stored Program
+ @details This macro creates a Stored Program along with the necessary precode
+ to enable the %webout() macro
+
+ Usage:
+
+ %* compile macros ;
+ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
+ %inc mc;
+
+ %* parmcards lets us write to a text file from open code ;
+ filename ft15f001 temp;
+ parmcards4;
+ %webout(FETCH)
+ %* do some sas, any inputs are now already WORK tables;
+ data example1 example2;
+ set sashelp.class;
+ run;
+ %* send data back;
+ %webout(OPEN)
+ %webout(ARR,example1) * Array format, fast, suitable for large tables ;
+ %webout(OBJ,example2) * Object format, easier to work with ;
+ %webout(CLOSE)
+ ;;;;
+ %ms_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001)
+
+
+
+ For more examples of using these web services with the SASjs Adapter, see:
+ https://github.com/sasjs/adapter#readme
+
+ @param [in] path= (0) The full SASjs Drive path in which to create the service
+ @param [in] name= Stored Program name
+ @param [in] desc= The description of the service (not implemented yet)
+ @param [in] precode= Space separated list of filerefs, pointing to the code
+ that needs to be attached to the beginning of the service (optional)
+ @param [in] code= (ft15f001) Space seperated fileref(s) of the actual code to
+ be added
+ @param [in] mDebug= (0) set to 1 to show debug messages in the log
+
+ SAS Macros
+ @li ms_createfile.sas
+ @li mf_getuser.sas
+ @li mf_getuniquename.sas
+ @li mf_getuniquefileref.sas
+ @li mp_abort.sas
+
+ Related Files
+ @li ms_createwebservice.test.sas
+
+ @version 9.2
+ @author Allan Bowe
+
+**/
+
+%macro ms_createwebservice(path=0
+ ,name=initService
+ ,precode=
+ ,code=ft15f001
+ ,desc=Not currently used
+ ,mDebug=0
+)/*/STORE SOURCE*/;
+
+%local dbg;
+%if &mdebug=1 %then %do;
+ %put &sysmacroname entry vars:;
+ %put _local_;
+%end;
+%else %let dbg=*;
+
+%mp_abort(iftrue=(&syscc ge 4 )
+ ,mac=ms_createwebservice
+ ,msg=%str(syscc=&syscc on macro entry)
+)
+%mp_abort(iftrue=("&path"="0")
+ ,mac=ms_createwebservice
+ ,msg=%str(Path not provided)
+)
+
+/* remove any trailing slash */
+%if "%substr(&path,%length(&path),1)" = "/" %then
+ %let path=%substr(&path,1,%length(&path)-1);
+
+/**
+ * Add webout macro
+ * These put statements are auto generated - to change the macro, change the
+ * source (ms_webout) and run `build.py`
+ */
+%local sasjsref;
+%let sasjsref=%mf_getuniquefileref();
+data _null_;
+ file &sasjsref termstr=crlf lrecl=512;
+ put "/* Created on %sysfunc(datetime(),datetime19.) by %mf_getuser() */";
+/* WEBOUT BEGIN */
+ put ' ';
+ put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y ';
+ put ' ,engine=DATASTEP ';
+ put ' ,missing=NULL ';
+ put ' ,showmeta=NO ';
+ put ')/*/STORE SOURCE*/; ';
+ put '%local tempds colinfo fmtds i numcols; ';
+ put '%let numcols=0; ';
+ put ' ';
+ put '%if &action=OPEN %then %do; ';
+ put ' options nobomfile; ';
+ put ' data _null_;file &jref encoding=''utf-8'' lrecl=200; ';
+ put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
+ put ' run; ';
+ put '%end; ';
+ put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
+ put ' options validvarname=upcase; ';
+ put ' data _null_; file &jref encoding=''utf-8'' mod; ';
+ put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
+ put ' ';
+ put ' /* grab col defs */ ';
+ put ' proc contents noprint data=&ds ';
+ put ' out=_data_(keep=name type length format formatl formatd varnum label); ';
+ put ' run; ';
+ put ' %let colinfo=%scan(&syslast,2,.); ';
+ put ' proc sort data=&colinfo; ';
+ put ' by varnum; ';
+ put ' run; ';
+ put ' /* move meta to mac vars */ ';
+ put ' data _null_; ';
+ put ' if _n_=1 then call symputx(''numcols'',nobs,''l''); ';
+ put ' set &colinfo end=last nobs=nobs; ';
+ put ' name=upcase(name); ';
+ put ' /* fix formats */ ';
+ put ' if type=2 or type=6 then do; ';
+ put ' typelong=''char''; ';
+ put ' length fmt $49.; ';
+ put ' if format='''' then fmt=cats(''$'',length,''.''); ';
+ put ' else if formatl=0 then fmt=cats(format,''.''); ';
+ put ' else fmt=cats(format,formatl,''.''); ';
+ put ' newlen=max(formatl,length); ';
+ put ' end; ';
+ put ' else do; ';
+ put ' typelong=''num''; ';
+ put ' if format='''' then fmt=''best.''; ';
+ put ' else if formatl=0 then fmt=cats(format,''.''); ';
+ put ' else if formatd=0 then fmt=cats(format,formatl,''.''); ';
+ put ' else fmt=cats(format,formatl,''.'',formatd); ';
+ put ' /* needs to be wide, for datetimes etc */ ';
+ put ' newlen=max(length,formatl,24); ';
+ put ' end; ';
+ put ' /* 32 char unique name */ ';
+ put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); ';
+ put ' ';
+ put ' call symputx(cats(''name'',_n_),name,''l''); ';
+ put ' call symputx(cats(''newname'',_n_),newname,''l''); ';
+ put ' call symputx(cats(''len'',_n_),newlen,''l''); ';
+ put ' call symputx(cats(''length'',_n_),length,''l''); ';
+ put ' call symputx(cats(''fmt'',_n_),fmt,''l''); ';
+ put ' call symputx(cats(''type'',_n_),type,''l''); ';
+ put ' call symputx(cats(''typelong'',_n_),typelong,''l''); ';
+ put ' call symputx(cats(''label'',_n_),coalescec(label,name),''l''); ';
+ put ' run; ';
+ put ' ';
+ put ' %let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
+ put ' ';
+ put ' %if &engine=PROCJSON %then %do; ';
+ put ' %if &missing=STRING %then %do; ';
+ put ' %put &sysmacroname: Special Missings not supported in proc json.; ';
+ put ' %put &sysmacroname: Switching to DATASTEP engine; ';
+ put ' %goto datastep; ';
+ put ' %end; ';
+ put ' data &tempds;set &ds; ';
+ put ' %if &fmt=N %then format _numeric_ best32.;; ';
+ put ' /* PRETTY is necessary to avoid line truncation in large files */ ';
+ put ' proc json out=&jref pretty ';
+ put ' %if &action=ARR %then nokeys ; ';
+ put ' ;export &tempds / nosastags fmtnumeric; ';
+ put ' run; ';
+ put ' %end; ';
+ put ' %else %if &engine=DATASTEP %then %do; ';
+ put ' %datastep: ';
+ put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 ';
+ put ' %then %do; ';
+ put ' %put &sysmacroname: &ds NOT FOUND!!!; ';
+ put ' %return; ';
+ put ' %end; ';
+ put ' ';
+ put ' %if &fmt=Y %then %do; ';
+ put ' data _data_; ';
+ put ' /* rename on entry */ ';
+ put ' set &ds(rename=( ';
+ put ' %do i=1 %to &numcols; ';
+ put ' &&name&i=&&newname&i ';
+ put ' %end; ';
+ put ' )); ';
+ put ' %do i=1 %to &numcols; ';
+ put ' length &&name&i $&&len&i; ';
+ put ' %if &&typelong&i=num %then %do; ';
+ put ' &&name&i=left(put(&&newname&i,&&fmt&i)); ';
+ put ' %end; ';
+ put ' %else %do; ';
+ put ' &&name&i=put(&&newname&i,&&fmt&i); ';
+ put ' %end; ';
+ put ' drop &&newname&i; ';
+ put ' %end; ';
+ put ' if _error_ then call symputx(''syscc'',1012); ';
+ put ' run; ';
+ put ' %let fmtds=&syslast; ';
+ put ' %end; ';
+ put ' ';
+ put ' proc format; /* credit yabwon for special null removal */ ';
+ put ' value bart (default=40) ';
+ put ' %if &missing=NULL %then %do; ';
+ put ' ._ - .z = null ';
+ put ' %end; ';
+ put ' %else %do; ';
+ put ' ._ = [quote()] ';
+ put ' . = null ';
+ put ' .a - .z = [quote()] ';
+ put ' %end; ';
+ put ' other = [best.]; ';
+ put ' ';
+ put ' data &tempds; ';
+ put ' attrib _all_ label=''''; ';
+ put ' %do i=1 %to &numcols; ';
+ put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
+ put ' length &&name&i $32767; ';
+ put ' format &&name&i $32767.; ';
+ put ' %end; ';
+ put ' %end; ';
+ put ' %if &fmt=Y %then %do; ';
+ put ' set &fmtds; ';
+ put ' %end; ';
+ put ' %else %do; ';
+ put ' set &ds; ';
+ put ' %end; ';
+ put ' format _numeric_ bart.; ';
+ put ' %do i=1 %to &numcols; ';
+ put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
+ put ' if findc(&&name&i,''"\''!!''0A0D09000E0F01021011''x) then do; ';
+ put ' &&name&i=''"''!!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/\\/\\\\/'',-1,&&name&i) ';
+ put ' ))))))))))))!!''"''; ';
+ put ' end; ';
+ put ' else &&name&i=quote(cats(&&name&i)); ';
+ put ' %end; ';
+ put ' %end; ';
+ put ' run; ';
+ put ' ';
+ put ' /* write to temp loc to avoid _webout truncation ';
+ put ' - https://support.sas.com/kb/49/325.html */ ';
+ put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
+ put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod ; ';
+ put ' if _n_=1 then put "["; ';
+ put ' set &tempds; ';
+ put ' if _n_>1 then put "," @; put ';
+ put ' %if &action=ARR %then "[" ; %else "{" ; ';
+ put ' %do i=1 %to &numcols; ';
+ put ' %if &i>1 %then "," ; ';
+ put ' %if &action=OBJ %then """&&name&i"":" ; ';
+ put ' &&name&i ';
+ put ' %end; ';
+ put ' %if &action=ARR %then "]" ; %else "}" ; ; ';
+ put ' /* now write the long strings to _webout 1 byte at a time */ ';
+ put ' data _null_; ';
+ put ' length filein 8 fileid 8; ';
+ put ' filein=fopen("_sjs",''I'',1,''B''); ';
+ put ' fileid=fopen("&jref",''A'',1,''B''); ';
+ put ' rec=''20''x; ';
+ put ' do while(fread(filein)=0); ';
+ put ' rc=fget(filein,rec,1); ';
+ put ' rc=fput(fileid, rec); ';
+ put ' rc=fwrite(fileid); ';
+ put ' end; ';
+ put ' /* close out the table */ ';
+ put ' rc=fput(fileid, "]"); ';
+ put ' rc=fwrite(fileid); ';
+ put ' rc=fclose(filein); ';
+ put ' rc=fclose(fileid); ';
+ put ' run; ';
+ put ' filename _sjs clear; ';
+ put ' %end; ';
+ put ' ';
+ put ' proc sql; ';
+ put ' drop table &colinfo, &tempds; ';
+ put ' ';
+ put ' %if &showmeta=YES %then %do; ';
+ put ' data _null_; file &jref encoding=''utf-8'' mod; ';
+ put ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; ';
+ put ' do i=1 to &numcols; ';
+ put ' name=quote(trim(symget(cats(''name'',i)))); ';
+ put ' format=quote(trim(symget(cats(''fmt'',i)))); ';
+ put ' label=quote(trim(symget(cats(''label'',i)))); ';
+ put ' length=quote(trim(symget(cats(''length'',i)))); ';
+ put ' type=quote(trim(symget(cats(''typelong'',i)))); ';
+ put ' if i>1 then put "," @@; ';
+ put ' put name '':{"format":'' format '',"label":'' label ';
+ put ' '',"length":'' length '',"type":'' type ''}''; ';
+ put ' end; ';
+ put ' put ''}}''; ';
+ put ' run; ';
+ put ' %end; ';
+ put '%end; ';
+ put ' ';
+ put '%else %if &action=CLOSE %then %do; ';
+ put ' data _null_; file &jref encoding=''utf-8'' mod ; ';
+ put ' put "}"; ';
+ put ' run; ';
+ put '%end; ';
+ put '%mend mp_jsonout; ';
+ put ' ';
+ put '%macro mf_getuser(type=META ';
+ put ')/*/STORE SOURCE*/; ';
+ put ' %local user metavar; ';
+ put ' %if &type=OS %then %let metavar=_secureusername; ';
+ put ' %else %let metavar=_metaperson; ';
+ put ' ';
+ put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
+ put ' %else %if %symexist(&metavar) %then %do; ';
+ put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
+ put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
+ put ' /* but be sure to quote in case of usernames with commas */ ';
+ put ' %else %let user=%unquote(%scan(%quote(&&&metavar),1,@)); ';
+ put ' %end; ';
+ put ' %else %let user=&sysuserid; ';
+ put ' ';
+ put ' %quote(&user) ';
+ put ' ';
+ put '%mend mf_getuser; ';
+ put ' ';
+ put '%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL ';
+ put ' ,showmeta=NO ';
+ put '); ';
+ put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug ';
+ put ' sasjs_tables; ';
+ put ' ';
+ put '%local i tempds; ';
+ put '%let action=%upcase(&action); ';
+ put ' ';
+ put '%if &action=FETCH %then %do; ';
+ put ' %if %str(&_debug) ge 131 %then %do; ';
+ put ' options mprint notes mprintnest; ';
+ put ' %end; ';
+ put ' %let _webin_file_count=%eval(&_webin_file_count+0); ';
+ put ' /* now read in the data */ ';
+ put ' %do i=1 %to &_webin_file_count; ';
+ put ' %if &_webin_file_count=1 %then %do; ';
+ put ' %let _webin_fileref1=&_webin_fileref; ';
+ put ' %let _webin_name1=&_webin_name; ';
+ put ' %end; ';
+ put ' data _null_; ';
+ put ' infile &&_webin_fileref&i termstr=crlf lrecl=32767; ';
+ put ' input; ';
+ put ' call symputx(''input_statement'',_infile_); ';
+ put ' putlog "&&_webin_name&i input statement: " _infile_; ';
+ put ' stop; ';
+ put ' data &&_webin_name&i; ';
+ put ' infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'' ';
+ put ' lrecl=32767; ';
+ put ' input &input_statement; ';
+ put ' %if %str(&_debug) ge 131 %then %do; ';
+ put ' if _n_<20 then putlog _infile_; ';
+ put ' %end; ';
+ put ' run; ';
+ put ' %let sasjs_tables=&sasjs_tables &&_webin_name&i; ';
+ put ' %end; ';
+ put '%end; ';
+ put ' ';
+ put '%else %if &action=OPEN %then %do; ';
+ put ' /* fix encoding and ensure enough lrecl */ ';
+ put ' OPTIONS NOBOMFILE lrecl=32767; ';
+ put ' ';
+ put ' /* set the header */ ';
+ put ' %mfs_httpheader(Content-type,application/json) ';
+ put ' ';
+ put ' /* setup json. */ ';
+ put ' data _null_;file &fref encoding=''utf-8'' termstr=lf ; ';
+ put ' put ''{"SYSDATE" : "'' "&SYSDATE" ''"''; ';
+ put ' put '',"SYSTIME" : "'' "&SYSTIME" ''"''; ';
+ put ' run; ';
+ put ' ';
+ put '%end; ';
+ put ' ';
+ put '%else %if &action=ARR or &action=OBJ %then %do; ';
+ put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref ';
+ put ' ,engine=DATASTEP,missing=&missing,showmeta=&showmeta ';
+ put ' ) ';
+ put '%end; ';
+ put '%else %if &action=CLOSE %then %do; ';
+ put ' %if %str(&_debug) ge 131 %then %do; ';
+ put ' /* if debug mode, send back first 10 records of each work table also */ ';
+ put ' options obs=10; ';
+ put ' data;run;%let tempds=%scan(&syslast,2,.); ';
+ put ' ods output Members=&tempds; ';
+ put ' proc datasets library=WORK memtype=data; ';
+ put ' %local wtcnt;%let wtcnt=0; ';
+ put ' data _null_; ';
+ put ' set &tempds; ';
+ put ' if not (upcase(name) =:"DATA"); /* ignore temp datasets */ ';
+ put ' if not (upcase(name)=:"_DATA_"); ';
+ put ' i+1; ';
+ put ' call symputx(cats(''wt'',i),name,''l''); ';
+ put ' call symputx(''wtcnt'',i,''l''); ';
+ put ' data _null_; file &fref mod encoding=''utf-8'' termstr=lf; ';
+ put ' put ",""WORK"":{"; ';
+ put ' %do i=1 %to &wtcnt; ';
+ put ' %let wt=&&wt&i; ';
+ put ' data _null_; file &fref mod encoding=''utf-8'' termstr=lf; ';
+ put ' dsid=open("WORK.&wt",''is''); ';
+ put ' nlobs=attrn(dsid,''NLOBS''); ';
+ put ' nvars=attrn(dsid,''NVARS''); ';
+ put ' rc=close(dsid); ';
+ put ' if &i>1 then put '',''@; ';
+ put ' put " ""&wt"" : {"; ';
+ put ' put ''"nlobs":'' nlobs; ';
+ put ' put '',"nvars":'' nvars; ';
+ put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES) ';
+ put ' data _null_; file &fref mod encoding=''utf-8'' termstr=lf; ';
+ put ' put "}"; ';
+ put ' %end; ';
+ put ' data _null_; file &fref mod encoding=''utf-8'' termstr=lf; ';
+ put ' put "}"; ';
+ put ' run; ';
+ put ' %end; ';
+ put ' /* close off json */ ';
+ put ' data _null_;file &fref mod encoding=''utf-8'' termstr=lf lrecl=32767; ';
+ put ' _PROGRAM=quote(trim(resolve(symget(''_PROGRAM'')))); ';
+ put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
+ put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
+ put ' put ",""_DEBUG"" : ""&_debug"" "; ';
+ put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
+ put ' put ",""SYSCC"" : ""&syscc"" "; ';
+ put ' syserrortext=quote(cats(symget(''SYSERRORTEXT''))); ';
+ put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
+ put ' SYSHOSTINFOLONG=quote(trim(symget(''SYSHOSTINFOLONG''))); ';
+ put ' put '',"SYSHOSTINFOLONG" : '' SYSHOSTINFOLONG; ';
+ put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
+ put ' put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" "; ';
+ put ' put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" "; ';
+ put ' length SYSPROCESSNAME $512; ';
+ put ' SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME))); ';
+ put ' put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME; ';
+ put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
+ put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
+ put ' put ",""SYSSITE"" : ""&syssite"" "; ';
+ put ' put ",""SYSTCPIPHOSTNAME"" : ""&SYSTCPIPHOSTNAME"" "; ';
+ put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
+ put ' put '',"SYSVLONG" : '' sysvlong; ';
+ put ' syswarningtext=quote(cats(symget(''SYSWARNINGTEXT''))); ';
+ put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; ';
+ put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
+ put ' length autoexec $512; ';
+ put ' autoexec=quote(urlencode(trim(getoption(''autoexec'')))); ';
+ put ' put '',"AUTOEXEC" : '' autoexec; ';
+ put ' length memsize $32; ';
+ put ' memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)"; ';
+ put ' memsize=quote(cats(memsize)); ';
+ put ' put '',"MEMSIZE" : '' memsize; ';
+ put ' put "}" @; ';
+ put ' run; ';
+ put '%end; ';
+ put ' ';
+ put '%mend ms_webout; ';
+ put ' ';
+ put '%macro mfs_httpheader(header_name ';
+ put ' ,header_value ';
+ put ')/*/STORE SOURCE*/; ';
+ put '%global sasjs_stpsrv_header_loc; ';
+ put '%local fref fid i; ';
+ put ' ';
+ put '%if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc)) ne 0 %then %do; ';
+ put ' %put &=fref &=sasjs_stpsrv_header_loc; ';
+ put ' %put %str(ERR)OR: %sysfunc(sysmsg()); ';
+ put ' %return; ';
+ put '%end; ';
+ put ' ';
+ put '%let fid=%sysfunc(fopen(&fref,A)); ';
+ put ' ';
+ put '%if &fid=0 %then %do; ';
+ put ' %put %str(ERR)OR: %sysfunc(sysmsg()); ';
+ put ' %return; ';
+ put '%end; ';
+ put ' ';
+ put '%let rc=%sysfunc(fput(&fid,%str(&header_name): %str(&header_value))); ';
+ put '%let rc=%sysfunc(fwrite(&fid)); ';
+ put ' ';
+ put '%let rc=%sysfunc(fclose(&fid)); ';
+ put '%let rc=%sysfunc(filename(&fref)); ';
+ put ' ';
+ put '%mend mfs_httpheader; ';
+/* WEBOUT END */
+ put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO);';
+ put ' %ms_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing';
+ put ' ,showmeta=&showmeta';
+ put ' )';
+ put '%mend;';
+run;
+
+/* add precode and code */
+%local x fref freflist;
+%let freflist=&precode &code ;
+%do x=1 %to %sysfunc(countw(&freflist));
+ %let fref=%scan(&freflist,&x);
+ %put &sysmacroname: adding &fref;
+ data _null_;
+ file &sasjsref lrecl=3000 termstr=crlf mod;
+ infile &fref lrecl=3000;
+ input;
+ put _infile_;
+ run;
+%end;
+
+/* create the web service */
+%ms_createfile(&path/&name..sas, inref=&sasjsref, mdebug=&mdebug)
+
+%put ;%put ;%put ;%put ;%put ;%put ;
+%put &sysmacroname: STP &name successfully created in &path;
+%put ;%put ;%put ;
+%put Check it out here:;
+%put ;%put ;%put ;
+%put &_sasjs_apiserverurl.&_sasjs_apipath?_PROGRAM=&path/&name;
+%put ;%put ;%put ;%put ;%put ;%put ;
+
+%mend ms_createwebservice;
/**
@file
@brief Deletes a file from SASjs Drive
@@ -19388,48 +20158,154 @@ options &optval;
parameter)
@param [in] debug= (131) The value to supply to the _debug URL parameter
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
+ @param [in] inputparams=(_null_) A dataset containing name/value pairs in the
+ following format:
+ |name:$32|value:$10000|
+ |---|---|
+ |stpmacname|some value|
+ |mustbevalidname|can be anything, oops, %abort!!|
+ @param [in] inputfiles= (_null_) A dataset containing fileref/name/filename in
+ the following format:
+ |fileref:$8|name:$32|filename:$256|
+ |---|---|--|
+ |someref|some_name|some_filename.xls|
+ |fref2|another_file|zyx_v2.csv|
+
@param [out] outref= (outweb) The output fileref to contain the response JSON
(will be created using temp engine)
+ @param [out] outlogds= (_null_) Set to the name of a dataset to contain the
+ log. Table format:
+ |line:$2000|
+ |---|
+ |log line 1|
+ |log line 2|
SAS Macros
@li mf_getuniquefileref.sas
+ @li mf_getuniquelibref.sas
@li mp_abort.sas
**/
%macro ms_runstp(pgm
,debug=131
+ ,inputparams=_null_
+ ,inputfiles=_null_
,outref=outweb
+ ,outlogds=_null_
,mdebug=0
);
-%local dbg fname1;
+%local dbg mainref authref boundary;
+%let mainref=%mf_getuniquefileref();
+%let authref=%mf_getuniquefileref();
+%let boundary=%mf_getuniquename();
+%if &inputparams=0 %then %let inputparams=_null_;
+
%if &mdebug=1 %then %do;
%put &sysmacroname entry vars:;
%put _local_;
%end;
%else %let dbg=*;
-%let fname1=%mf_getuniquefileref();
+
%mp_abort(iftrue=("&pgm"="")
,mac=&sysmacroname
,msg=%str(Program not provided)
)
+/* avoid sending bom marker to API */
+%local optval;
+%let optval=%sysfunc(getoption(bomfile));
+options nobomfile;
+
+/* add params */
data _null_;
- file &fname1 lrecl=1000;
+ file &mainref termstr=crlf lrecl=32767 mod;
+ length line $1000 name $32 value $32767;
+ if _n_=1 then call missing(of _all_);
+ set &inputparams;
+ put "--&boundary";
+ line=cats('Content-Disposition: form-data; name="',name,'"');
+ put line;
+ put ;
+ put value;
+run;
+
+/* parse input file list */
+%local webcount;
+%let webcount=0;
+data _null_;
+ set &inputfiles end=last;
+ length fileref $8 name $32 filename $256;
+ call symputx(cats('webref',_n_),fileref,'l');
+ call symputx(cats('webname',_n_),name,'l');
+ call symputx(cats('webfilename',_n_),filename,'l');
+ if last then do;
+ call symputx('webcount',_n_);
+ call missing(of _all_);
+ end;
+run;
+
+/* write out the input files */
+%local i;
+%do i=1 %to &webcount;
+ data _null_;
+ file &mainref termstr=crlf lrecl=32767 mod;
+ infile &&webref&i lrecl=32767;
+ if _n_ = 1 then do;
+ length line $32767;
+ line=cats(
+ 'Content-Disposition: form-data; name="'
+ ,"&&webname&i"
+ ,'"; filename="'
+ ,"&&webfilename&i"
+ ,'"'
+ );
+ put "--&boundary";
+ put line;
+ put "Content-Type: text/plain";
+ put ;
+ end;
+ input;
+ put _infile_; /* add the actual file to be sent */
+ run;
+%end;
+
+data _null_;
+ file &mainref termstr=crlf mod;
+ put "--&boundary--";
+run;
+
+data _null_;
+ file &authref lrecl=1000;
infile "&_sasjs_tokenfile" lrecl=1000;
input;
put 'Authorization: Bearer ' _infile_;
+ put "Content-Type: multipart/form-data; boundary=&boundary";
run;
-filename &outref temp;
+%if &mdebug=1 %then %do;
+ data _null_;
+ infile &authref;
+ input;
+ put _infile_;
+ data _null_;
+ infile &mainref;
+ input;
+ put _infile_;
+ run;
+%end;
+filename &outref temp lrecl=32767;
/* prepare request*/
-proc http method='POST' headerin=&fname1 out=&outref
+proc http method='POST' headerin=&authref in=&mainref out=&outref
url="&_sasjs_apiserverurl.&_sasjs_apipath?_program=&pgm%str(&)_debug=131";
+%if &mdebug=1 %then %do;
+ debug level=2;
+%end;
run;
%if (&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201)
- or &mdebug=1 %then %do;
+%then %do;
data _null_;infile &outref;input;putlog _infile_;run;
%end;
%mp_abort(
@@ -19438,6 +20314,20 @@ run;
,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
)
+/* reset options */
+options &optval;
+
+%if &outlogds ne _null_ or &mdebug=1 %then %do;
+ %local dumplib;
+ %let dumplib=%mf_getuniquelibref();
+ libname &dumplib json (&outref);
+ data &outlogds;
+ set &dumplib..log;
+ %if &mdebug=1 %then %do;
+ putlog line=;
+ %end;
+ run;
+%end;
%if &mdebug=1 %then %do;
%put &sysmacroname exit vars:;
@@ -19445,9 +20335,161 @@ run;
%end;
%else %do;
/* clear refs */
- filename &fname1 clear;
+ filename &authref;
+ filename &mainref;
%end;
%mend ms_runstp;/**
+ @file
+ @brief Will execute a SASjs web service on SASjs Server
+ @details Prepares the input files and retrieves the resulting datasets from
+ the response JSON.
+
+ @param [in] program The Stored Program endpoint to test
+ @param [in] inputfiles=(0) A list of space seperated fileref:filename pairs as
+ follows:
+ inputfiles=inref:filename inref2:filename2
+ @param [in] inputdatasets= (0) All datasets in this space seperated list are
+ converted into SASJS-formatted CSVs (see mp_ds2csv.sas) files and added to
+ the list of `inputfiles` for ingestion. The dataset will be sent with the
+ same name (no need for a colon modifier).
+ @param [in] inputparams=(0) A dataset containing name/value pairs in the
+ following format:
+ |name:$32|value:$1000|
+ |---|---|
+ |stpmacname|some value|
+ |mustbevalidname|can be anything, oops, %abort!!|
+
+ @param [in] debug= (131) Provide the _debug value to pass to the STP
+ @param [in] mdebug= (0) Set to 1 to provide macro debugging (this macro)
+ @param [out] outlib= (0) Output libref to contain the final tables. Set to
+ 0 if the service output is not in JSON format.
+ @param [out] outref= (0) Output fileref to create, to contain the full _webout
+ response.
+ @param [out] outlogds= (_null_) Set to the name of a dataset to contain the
+ log. Table format:
+ |line:$2000|
+ |---|
+ |log line 1|
+ |log line 2|
+
+ SAS Macros
+ @li mf_getuniquefileref.sas
+ @li mf_getuniquename.sas
+ @li mp_abort.sas
+ @li mp_binarycopy.sas
+ @li mp_chop.sas
+ @li mp_ds2csv.sas
+ @li ms_runstp.sas
+
+ Related Programs
+ @li mp_testservice.test.sas
+
+ @version 9.4
+ @author Allan Bowe
+
+**/
+
+%macro ms_testservice(program,
+ inputfiles=0,
+ inputdatasets=0,
+ inputparams=0,
+ debug=0,
+ mdebug=0,
+ outlib=0,
+ outref=0,
+ outlogds=_null_
+)/*/STORE SOURCE*/;
+%local dbg fref1 chopout1 chopout2;
+%if &mdebug=1 %then %do;
+ %put &sysmacroname entry vars:;
+ %put _local_;
+%end;
+%else %let dbg=*;
+
+/* convert inputdatasets to filerefs */
+%if "&inputdatasets" ne "0" %then %do;
+ %if %quote(&inputfiles)=0 %then %let inputfiles=;
+ %do i=1 %to %sysfunc(countw(&inputdatasets,%str( )));
+ %let var=%scan(&inputdatasets,&i,%str( ));
+ %local dsref&i;
+ %let dsref&i=%mf_getuniquefileref();
+ %mp_ds2csv(&var,outref=&&dsref&i,headerformat=SASJS)
+ %let inputfiles=&inputfiles &&dsref&i:%scan(&var,-1,.);
+ %end;
+%end;
+
+/* parse the filerefs - convert to a dataset */
+%let ds1=%mf_getuniquename();
+data &ds1;
+ length fileref $8 name $32 filename $256 var $300;
+ webcount=countw("&inputfiles");
+ do i=1 to webcount;
+ var=scan("&inputfiles",i,' ');
+ fileref=scan(var,1,':');
+ name=scan(var,2,':');
+ filename=cats(name,'.csv');
+ output;
+ end;
+run;
+
+
+/* execute the STP */
+%let fref1=%mf_getuniquefileref();
+
+%ms_runstp(&program
+ ,debug=&debug
+ ,inputparams=&inputparams
+ ,inputfiles=&ds1
+ ,outref=&fref1
+ ,mdebug=&mdebug
+ ,outlogds=&outlogds
+)
+
+
+/* SASjs services have the _webout embedded in wrapper JSON */
+/* Files can also be very large - so use a dedicated macro to chop it out */
+%local matchstr1 matchstr2 ;
+%let matchstr1={"status":"success","_webout":{;
+%let matchstr2=},"log":[{;
+%let chopout1=%sysfunc(pathname(work))/%mf_getuniquename(prefix=chop1);
+%let chopout2=%sysfunc(pathname(work))/%mf_getuniquename(prefix=chop2);
+
+%mp_chop("%sysfunc(pathname(&fref1,F))"
+ ,matchvar=matchstr1
+ ,keep=LAST
+ ,matchpoint=END
+ ,offset=-1
+ ,outfile="&chopout1"
+ ,mdebug=&mdebug
+)
+
+%mp_chop("&chopout1"
+ ,matchvar=matchstr2
+ ,keep=FIRST
+ ,matchpoint=START
+ ,offset=1
+ ,outfile="&chopout2"
+ ,mdebug=&mdebug
+)
+
+%if &outlib ne 0 %then %do;
+ libname &outlib json "&chopout2";
+%end;
+%if &outref ne 0 %then %do;
+ filename &outref "&chopout2";
+%end;
+
+%if &mdebug=0 %then %do;
+ filename &webref clear;
+ filename &fref1 clear;
+ filename &fref2 clear;
+%end;
+%else %do;
+ %put &sysmacroname exit vars:;
+ %put _local_;
+%end;
+
+%mend ms_testservice;/**
@file
@brief Send data to/from sasjs/server
@details This macro should be added to the start of each web service,
@@ -19516,13 +20558,14 @@ run;
%let _webin_name1=&_webin_name;
%end;
data _null_;
- infile &&_webin_fileref&i termstr=crlf;
+ infile &&_webin_fileref&i termstr=crlf lrecl=32767;
input;
call symputx('input_statement',_infile_);
putlog "&&_webin_name&i input statement: " _infile_;
stop;
data &&_webin_name&i;
- infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding='utf-8';
+ infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding='utf-8'
+ lrecl=32767;
input &input_statement;
%if %str(&_debug) ge 131 %then %do;
if _n_<20 then putlog _infile_;
@@ -19533,14 +20576,14 @@ run;
%end;
%else %if &action=OPEN %then %do;
- /* fix encoding */
- OPTIONS NOBOMFILE;
+ /* fix encoding and ensure enough lrecl */
+ OPTIONS NOBOMFILE lrecl=32767;
/* set the header */
%mfs_httpheader(Content-type,application/json)
- /* setup json */
- data _null_;file &fref encoding='utf-8' termstr=lf;
+ /* setup json. */
+ data _null_;file &fref encoding='utf-8' termstr=lf ;
put '{"SYSDATE" : "' "&SYSDATE" '"';
put ',"SYSTIME" : "' "&SYSTIME" '"';
run;
@@ -19584,12 +20627,12 @@ run;
data _null_; file &fref mod encoding='utf-8' termstr=lf;
put "}";
%end;
- data _null_; file &fref mod encoding='utf-8' termstr=lf termstr=lf;
+ data _null_; file &fref mod encoding='utf-8' termstr=lf;
put "}";
run;
%end;
/* close off json */
- data _null_;file &fref mod encoding='utf-8' termstr=lf;
+ data _null_;file &fref mod encoding='utf-8' termstr=lf lrecl=32767;
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ",""SYSUSERID"" : ""&sysuserid"" ";
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
@@ -20611,7 +21654,7 @@ data _null_;
put ' ';
put '%if &action=OPEN %then %do; ';
put ' options nobomfile; ';
- put ' data _null_;file &jref encoding=''utf-8'' ; ';
+ put ' data _null_;file &jref encoding=''utf-8'' lrecl=200; ';
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
put ' run; ';
put '%end; ';
@@ -20822,6 +21865,25 @@ data _null_;
put ' run; ';
put '%end; ';
put '%mend mp_jsonout; ';
+ put ' ';
+ put '%macro mf_getuser(type=META ';
+ put ')/*/STORE SOURCE*/; ';
+ put ' %local user metavar; ';
+ put ' %if &type=OS %then %let metavar=_secureusername; ';
+ put ' %else %let metavar=_metaperson; ';
+ put ' ';
+ put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
+ put ' %else %if %symexist(&metavar) %then %do; ';
+ put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
+ put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
+ put ' /* but be sure to quote in case of usernames with commas */ ';
+ put ' %else %let user=%unquote(%scan(%quote(&&&metavar),1,@)); ';
+ put ' %end; ';
+ put ' %else %let user=&sysuserid; ';
+ put ' ';
+ put ' %quote(&user) ';
+ put ' ';
+ put '%mend mf_getuser; ';
put '%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y,missing=NULL ';
put ' ,showmeta=NO ';
put '); ';
@@ -20996,25 +22058,6 @@ data _null_;
put '%end; ';
put ' ';
put '%mend mv_webout; ';
- put ' ';
- put '%macro mf_getuser(type=META ';
- put ')/*/STORE SOURCE*/; ';
- put ' %local user metavar; ';
- put ' %if &type=OS %then %let metavar=_secureusername; ';
- put ' %else %let metavar=_metaperson; ';
- put ' ';
- put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
- put ' %else %if %symexist(&metavar) %then %do; ';
- put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
- put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
- put ' /* but be sure to quote in case of usernames with commas */ ';
- put ' %else %let user=%unquote(%scan(%quote(&&&metavar),1,@)); ';
- put ' %end; ';
- put ' %else %let user=&sysuserid; ';
- put ' ';
- put ' %quote(&user) ';
- put ' ';
- put '%mend mf_getuser; ';
/* WEBOUT END */
put '/* if calling viya service with _job param, _program will conflict */';
put '/* so it is provided by SASjs instead as __program */';
@@ -24846,8 +25889,16 @@ data _null_;
put 'io.close(file) ';
run;
+/* ensure big enough lrecl to avoid lua compilation issues */
+%local optval;
+%let optval=%sysfunc(getoption(lrecl));
+options lrecl=1024;
+
+/* execute the lua code by using a .lua extension */
%inc "%sysfunc(pathname(work))/ml_gsubfile.lua" /source2;
+options lrecl=&optval;
+
%mend ml_gsubfile;
/**
@file ml_json.sas
@@ -25240,8 +26291,16 @@ data _null_;
put '-- JSON.LUA ENDS HERE ';
run;
+/* ensure big enough lrecl to avoid lua compilation issues */
+%local optval;
+%let optval=%sysfunc(getoption(lrecl));
+options lrecl=1024;
+
+/* execute the lua code by using a .lua extension */
%inc "%sysfunc(pathname(work))/ml_json.lua" /source2;
+options lrecl=&optval;
+
%mend ml_json;
/**
@file
diff --git a/base/mf_getuniquefileref.sas b/base/mf_getuniquefileref.sas
index 8c11725..0c3447f 100644
--- a/base/mf_getuniquefileref.sas
+++ b/base/mf_getuniquefileref.sas
@@ -28,15 +28,17 @@
be 8 characters, so a 7 letter prefix would mean `maxtries` should be 10.
if using zero (0) as the prefix, a native assignment is used.
@param [in] maxtries= (1000) the last part of the libref. Must be an integer.
+ @param [in] lrecl= (32767) Provide a default lrecl with which to initialise
+ the generated fileref.
@version 9.2
@author Allan Bowe
**/
-%macro mf_getuniquefileref(prefix=_,maxtries=1000);
+%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);
%local rc fname;
%if &prefix=0 %then %do;
- %let rc=%sysfunc(filename(fname,,temp));
+ %let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));
%if &rc %then %put %sysfunc(sysmsg());
&fname
%end;
@@ -47,7 +49,7 @@
%do x=0 %to &maxtries;
%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);
%if %sysfunc(fileref(&fname)) > 0 %then %do;
- %let rc=%sysfunc(filename(fname,,temp));
+ %let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));
%if &rc %then %put %sysfunc(sysmsg());
&fname
%return;
diff --git a/base/mf_isint.sas b/base/mf_isint.sas
index 0cb7f87..71548bb 100644
--- a/base/mf_isint.sas
+++ b/base/mf_isint.sas
@@ -20,8 +20,11 @@
%macro mf_isint(arg
)/*/STORE SOURCE*/;
- /* remove minus sign if exists */
+ /* blank val is not an integer */
+ %if "&arg"="" %then %do;0%return;%end;
+
+ /* remove minus sign if exists */
%local val;
%if "%substr(%str(&arg),1,1)"="-" %then %let val=%substr(%str(&arg),2);
%else %let val=&arg;
diff --git a/base/mp_chop.sas b/base/mp_chop.sas
new file mode 100644
index 0000000..e5a0718
--- /dev/null
+++ b/base/mp_chop.sas
@@ -0,0 +1,194 @@
+/**
+ @file
+ @brief Splits a file of ANY SIZE by reference to a search string.
+ @details Provide a fileref and a search string to chop off part of a file.
+
+ Works by reading in the file byte by byte, then marking the beginning and end
+ of each matched string, before finally doing the chop.
+
+ Choose whether to keep the FIRST or the LAST section of the file. Optionally,
+ use an OFFSET to fix the precise chop point.
+
+ Usage:
+
+ %let src="%sysfunc(pathname(work))/file.txt";
+ %let str=Chop here!;
+ %let out1="%sysfunc(pathname(work))/file1.txt";
+ %let out2="%sysfunc(pathname(work))/file2.txt";
+ %let out3="%sysfunc(pathname(work))/file3.txt";
+ %let out4="%sysfunc(pathname(work))/file4.txt";
+
+ data _null_;
+ file &src;
+ put "startsection&str.endsection";
+ run;
+
+ %mp_chop(&src, matchvar=str, keep=FIRST, outfile=&out1)
+ %mp_chop(&src, matchvar=str, keep=LAST, outfile=&out2)
+ %mp_chop(&src, matchvar=str, keep=FIRST, matchpoint=END, outfile=&out3)
+ %mp_chop(&src, matchvar=str, keep=LAST, matchpoint=END, outfile=&out4)
+
+ filename results (&out1 &out2 &out3 &out4);
+ data _null_;
+ infile results;
+ input;
+ list;
+ run;
+
+ Results:
+ @li `startsection`
+ @li `Chop here!endsection`
+ @li `startsectionChop here!`
+ @li `endsection`
+
+ For more examples, see mp_chop.test.sas
+
+ @param [in] infile The QUOTED path to the file on which to perform the chop
+ @param [in] matchvar= Macro variable NAME containing the string to split by
+ @param [in] matchpoint= (START) Valid values:
+ @li START - chop at the beginning of the string in `matchvar`.
+ @li END - chop at the end of the string in `matchvar`.
+ @param [in] offset= (0) An adjustment to the precise chop location, by
+ by reference to the `matchpoint`. Should be a positive or negative integer.
+ @param [in] keep= (FIRST) Valid values:
+ @li FIRST - keep the section of the file before the chop
+ @li LAST - keep the section of the file after the chop
+ @param [in] mdebug= (0) Set to 1 to provide macro debugging
+ @param outfile= (0) Optional QUOTED path to the adjusted output file (avoids
+ overwriting the first file).
+
+ SAS Macros
+ @li mf_getuniquefileref.sas
+ @li mf_getuniquename.sas
+
+ Related Macros
+ @li mp_abort.sas
+ @li mp_gsubfile.sas
+ @li mp_replace.sas
+ @li mp_chop.test.sas
+
+ @version 9.4
+ @author Allan Bowe
+
+**/
+
+%macro mp_chop(infile,
+ matchvar=,
+ matchpoint=START,
+ keep=FIRST,
+ offset=0,
+ mdebug=0,
+ outfile=0
+)/*/STORE SOURCE*/;
+
+%local fref0 dttm ds1 outref;
+%let fref0=%mf_getuniquefileref();
+%let ds1=%mf_getuniquename(prefix=allchars);
+%let ds2=%mf_getuniquename(prefix=startmark);
+
+%if &outfile=0 %then %let outfile=&infile;
+
+%mp_abort(iftrue= (%length(%superq(&matchvar))=0)
+ ,mac=mp_chop.sas
+ ,msg=%str(&matchvar is an empty variable)
+)
+
+/* START */
+%let dttm=%sysfunc(datetime());
+
+filename &fref0 &infile lrecl=1 recfm=n;
+
+/* create dataset with one char per row */
+data &ds1;
+ infile &fref0;
+ input sourcechar $char1. @@;
+ format sourcechar hex2.;
+run;
+
+/* get start & stop position of first matchvar string (one row, two vars) */
+data &ds2;
+ /* set find string to length in bytes to cover trailing spaces */
+ length string $ %length(%superq(&matchvar));
+ string =symget("&matchvar");
+ drop string;
+
+ firstchar=char(string,1);
+ findlen=lengthm(string); /* <- for trailing bytes */
+
+ do _N_=1 to nobs;
+ set &ds1 nobs=nobs point=_N_;
+ if sourcechar=firstchar then do;
+ pos=1;
+ s=0;
+ do point=_N_ to min(_N_ + findlen -1,nobs);
+ set &ds1 point=point;
+ if sourcechar=char(string, pos) then s + 1;
+ else goto _leave_;
+ pos+1;
+ end;
+ _leave_:
+ if s=findlen then do;
+ START =_N_;
+ _N_ =_N_+ s - 1;
+ STOP =_N_;
+ output;
+ /* matched! */
+ stop;
+ end;
+ end;
+ end;
+ stop;
+ keep START STOP;
+run;
+
+%local split;
+%let split=0;
+data _null_;
+ set &ds2;
+ if "&matchpoint"='START' then do;
+ if "&keep"='FIRST' then mp=start;
+ else if "&keep"='LAST' then mp=start-1;
+ end;
+ else if "&matchpoint"='END' then do;
+ if "&keep"='FIRST' then mp=stop+1;
+ else if "&keep"='LAST' then mp=stop;
+ end;
+ split=mp+&offset;
+ call symputx('split',split,'l');
+%if &mdebug=1 %then %do;
+ put (_all_)(=);
+ %put &=offset;
+%end;
+run;
+%if &split=0 %then %do;
+ %put &sysmacroname: No match found in &infile for string %superq(&matchvar);
+ %return;
+%end;
+
+data _null_;
+ file &outfile recfm=n;
+ set &ds1;
+%if &keep=FIRST %then %do;
+ if _n_ ge &split then stop;
+%end;
+%else %do;
+ if _n_ gt &split;
+%end;
+ put sourcechar char1.;
+run;
+
+%if &mdebug=0 %then %do;
+ filename &fref0 clear;
+%end;
+%else %do;
+ data _null_;
+ infile &outfile lrecl=32767;
+ input;
+ list;
+ if _n_>50 then stop;
+ run;
+%end;
+/* END */
+%put &sysmacroname took %sysevalf(%sysfunc(datetime())-&dttm) seconds to run;
+
+%mend mp_chop;
diff --git a/base/mp_createwebservice.sas b/base/mp_createwebservice.sas
index a719f17..80b4459 100644
--- a/base/mp_createwebservice.sas
+++ b/base/mp_createwebservice.sas
@@ -29,6 +29,7 @@ Usage:
SAS Macros
@li mf_getplatform.sas
@li mm_createwebservice.sas
+ @li ms_createwebservice.sas
@li mv_createwebservice.sas
@param [in,out] path= The full folder path where the service will be created
@@ -40,7 +41,7 @@ Usage:
be added
@param [in] replace= (YES) Select YES to replace any existing service in that
location
-
+ @param [in] mDebug= (0) set to 1 to show debug messages in the log
@version 9.2
@author Allan Bowe
@@ -53,6 +54,7 @@ Usage:
,code=ft15f001
,desc=This service was created by the mp_createwebservice macro
,replace=YES
+ ,mdebug=0
)/*/STORE SOURCE*/;
%if &syscc ge 4 %then %do;
@@ -71,8 +73,17 @@ Usage:
,replace=&replace
)
%end;
+%else %if &platform=SASJS %then %do;
+ %if "&path"="HOME" %then %let path=/Users/&_sasjs_username/My Folder;
+ %ms_createwebservice(path=&path
+ ,name=&name
+ ,code=&code
+ ,precode=&precode
+ ,mdebug=&mdebug
+ )
+%end;
%else %do;
- %if "&path"="HOME" %then %let path=/User Folders/&sysuserid/My Folder;
+ %if "&path"="HOME" %then %let path=/User Folders/&_METAPERSON/My Folder;
%mm_createwebservice(path=&path
,name=&name
,code=&code
diff --git a/base/mp_gsubfile.sas b/base/mp_gsubfile.sas
index 8aa02ea..52dd887 100644
--- a/base/mp_gsubfile.sas
+++ b/base/mp_gsubfile.sas
@@ -48,7 +48,7 @@
outfile=0
)/*/STORE SOURCE*/;
- %if "%substr(&sysver,1,4)"="V.04" %then %do;
+ %if "%substr(&sysver.XX,1,4)"="V.04" %then %do;
%put %str(ERR)OR: Viya 4 does not support the IO library in lua;
%return;
%end;
diff --git a/base/mp_jsonout.sas b/base/mp_jsonout.sas
index bcb0a25..be2c7bc 100644
--- a/base/mp_jsonout.sas
+++ b/base/mp_jsonout.sas
@@ -70,7 +70,7 @@
%if &action=OPEN %then %do;
options nobomfile;
- data _null_;file &jref encoding='utf-8' ;
+ data _null_;file &jref encoding='utf-8' lrecl=200;
put '{"PROCESSED_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '"';
run;
%end;
diff --git a/base/mp_replace.sas b/base/mp_replace.sas
index a989e9e..32ea3d4 100644
--- a/base/mp_replace.sas
+++ b/base/mp_replace.sas
@@ -35,7 +35,7 @@
@param infile The QUOTED path to the file on which to perform the substitution
@param findvar= Macro variable NAME containing the string to search for
@param replacevar= Macro variable NAME containing the replacement string
- @param outfile= (0) Optional QUOTED path to an the adjusted output file (to
+ @param outfile= (0) Optional QUOTED path to the adjusted output file (to
avoid overwriting the first file).
SAS Macros
@@ -43,7 +43,9 @@
@li mf_getuniquename.sas
Related Macros
- @li mp_gsubfile.test.sas
+ @li mp_chop.sas
+ @li mp_gsubfile.sas
+ @li mp_replace.test.sas
@version 9.4
@author Bartosz Jabłoński
diff --git a/base/mp_testservice.sas b/base/mp_testservice.sas
index c6c2215..74b3ff6 100644
--- a/base/mp_testservice.sas
+++ b/base/mp_testservice.sas
@@ -40,7 +40,9 @@
@li mf_getuniquename.sas
@li mp_abort.sas
@li mp_binarycopy.sas
+ @li mp_chop.sas
@li mp_ds2csv.sas
+ @li ms_testservice.sas
@li mv_getjobresult.sas
@li mv_jobflow.sas
@@ -63,7 +65,7 @@
viyaresult=WEBOUT_JSON,
viyacontext=SAS Job Execution compute context
)/*/STORE SOURCE*/;
-%local dbg pcnt fref1 webref i webcount var platform;
+%local dbg pcnt fref1 fref2 webref webrefpath i webcount var platform;
%if &mdebug=1 %then %do;
%put &sysmacroname entry vars:;
%put _local_;
@@ -104,10 +106,14 @@
%end;
%end;
-
-%let fref1=%mf_getuniquefileref();
-%let webref=%mf_getuniquefileref();
%let platform=%mf_getplatform();
+%let fref1=%mf_getuniquefileref();
+%let fref2=%mf_getuniquefileref();
+%let webref=%mf_getuniquefileref();
+%let webrefpath=%sysfunc(pathname(work))/%mf_getuniquename();
+/* mp_chop requires a physical path as input */
+filename &webref "&webrefpath";
+
%if &platform=SASMETA %then %do;
/* parse the input files */
@@ -262,6 +268,19 @@
mdebug=&mdebug
)
+%end;
+%else %if &platform=SASJS %then %do;
+
+ %ms_testservice(&program
+ ,inputfiles=&inputfiles
+ ,inputdatasets=&inputdatasets
+ ,inputparams=&inputparams
+ ,debug=&debug
+ ,mdebug=&mdebug
+ ,outlib=&outlib
+ ,outref=&outref
+ )
+
%end;
%else %do;
%put %str(ERR)OR: Unrecognised platform: &platform;
@@ -269,6 +288,8 @@
%if &mdebug=0 %then %do;
filename &webref clear;
+ filename &fref1 clear;
+ filename &fref2 clear;
%end;
%else %do;
%put &sysmacroname exit vars:;
diff --git a/build.py b/build.py
index 7df8e39..c4ab568 100755
--- a/build.py
+++ b/build.py
@@ -4,8 +4,8 @@ from pathlib import Path
# Prepare Lua Macros
files = [f for f in Path('lua').iterdir() if f.match("*.lua")]
for file in files:
- basename=os.path.basename(file)
- name='ml_' + os.path.splitext(basename)[0]
+ basename = os.path.basename(file)
+ name = 'ml_' + os.path.splitext(basename)[0]
ml = open('lua/' + name + '.sas', "w")
ml.write("/**\n")
ml.write(" @file " + name + '.sas\n')
@@ -20,50 +20,68 @@ for file in files:
ml.write(" file \"%sysfunc(pathname(work))/" + name + ".lua\";\n")
with open(file) as infile:
for line in infile:
- ml.write(" put '" + line.rstrip().replace("'","''") + " ';\n")
+ ml.write(" put '" + line.rstrip().replace("'", "''") + " ';\n")
ml.write("run;\n\n")
- ml.write("%inc \"%sysfunc(pathname(work))/" + name + ".lua\" /source2;\n\n")
+
+ ml.write("/* ensure big enough lrecl to avoid lua compilation issues */\n")
+ ml.write("%local optval;\n")
+ ml.write("%let optval=%sysfunc(getoption(lrecl));\n")
+ ml.write("options lrecl=1024;\n\n")
+ ml.write("/* execute the lua code by using a .lua extension */\n")
+ ml.write("%inc \"%sysfunc(pathname(work))/" +
+ name + ".lua\" /source2;\n\n")
+ ml.write("options lrecl=&optval;\n\n")
ml.write("%mend " + name + ";\n")
ml.close()
# prepare web files
-files=['viya/mv_createwebservice.sas','meta/mm_createwebservice.sas']
+files = ['viya/mv_createwebservice.sas',
+ 'meta/mm_createwebservice.sas', 'server/ms_createwebservice.sas']
for file in files:
- webout0=open('base/mp_jsonout.sas','r')
- if file=='viya/mv_createwebservice.sas':
- webout1=open('viya/mv_webout.sas',"r")
+ webout0 = open('base/mp_jsonout.sas', 'r')
+ webout1 = open('base/mf_getuser.sas', 'r')
+
+ if file == 'viya/mv_createwebservice.sas':
+ webout2 = open('viya/mv_webout.sas', "r")
+ weboutfiles = [webout0, webout1, webout2]
+ elif file == 'server/ms_createwebservice.sas':
+ webout2 = open('server/ms_webout.sas', "r")
+ webout3 = open('server/mfs_httpheader.sas', 'r')
+ weboutfiles = [webout0, webout1, webout2, webout3]
else:
- webout1=open('meta/mm_webout.sas','r')
- webout2=open('base/mf_getuser.sas','r')
- outfile=open(file + 'TEMP','w')
- infile=open(file,'r')
- delrow=0
+ webout2 = open('meta/mm_webout.sas', 'r')
+ weboutfiles = [webout0, webout1, webout2]
+ outfile = open(file + 'TEMP', 'w')
+ infile = open(file, 'r')
+ delrow = 0
for line in infile:
- if line=='/* WEBOUT BEGIN */\n':
- delrow=1
+ if line == '/* WEBOUT BEGIN */\n':
+ delrow = 1
outfile.write('/* WEBOUT BEGIN */\n')
- weboutfiles=[webout0,webout1,webout2]
for weboutfile in weboutfiles:
- stripcomment=1
+ stripcomment = 1
for w in weboutfile:
- if w=='**/\n': stripcomment=0
- elif stripcomment==0:
- outfile.write(" put '" + w.rstrip().replace("'","''") + " ';\n")
- elif delrow==1 and line=='/* WEBOUT END */\n':
- delrow=0
- outfile.write('/* WEBOUT END */\n')
- elif delrow==0:
+ if w == '**/\n':
+ stripcomment = 0
+ elif stripcomment == 0:
+ outfile.write(
+ " put '" + w.rstrip().replace("'", "''") + " ';\n")
+ elif delrow == 1 and line == '/* WEBOUT END */\n':
+ delrow = 0
+ outfile.write('/* WEBOUT END */\n')
+ elif delrow == 0:
outfile.write(line.rstrip() + "\n")
webout0.close()
webout1.close()
+ webout2.close()
outfile.close()
infile.close()
os.remove(file)
- os.rename(file + 'TEMP',file)
+ os.rename(file + 'TEMP', file)
# Concatenate all macros into a single file
-header="""
+header = """
/**
@file
@brief Auto-generated file
@@ -84,14 +102,15 @@ options noquotelenmax;
"""
f = open('all.sas', "w") # r / r+ / rb / rb+ / w / wb
f.write(header)
-folders=['base','ddl','meta','metax','server','viya','lua','fcmp']
+folders = ['base', 'ddl', 'meta', 'metax', 'server', 'viya', 'lua', 'fcmp']
for folder in folders:
- filenames = [fn for fn in Path('./' + folder).iterdir() if fn.match("*.sas")]
+ filenames = [fn for fn in Path(
+ './' + folder).iterdir() if fn.match("*.sas")]
filenames.sort()
with open('mc_' + folder + '.sas', 'w') as outfile:
for fname in filenames:
with open(fname) as infile:
outfile.write(infile.read())
- with open('mc_' + folder + '.sas','r') as c:
+ with open('mc_' + folder + '.sas', 'r') as c:
f.write(c.read())
f.close()
diff --git a/lua/ml_gsubfile.sas b/lua/ml_gsubfile.sas
index 41eef30..c2b873f 100644
--- a/lua/ml_gsubfile.sas
+++ b/lua/ml_gsubfile.sas
@@ -39,6 +39,14 @@ data _null_;
put 'io.close(file) ';
run;
+/* ensure big enough lrecl to avoid lua compilation issues */
+%local optval;
+%let optval=%sysfunc(getoption(lrecl));
+options lrecl=1024;
+
+/* execute the lua code by using a .lua extension */
%inc "%sysfunc(pathname(work))/ml_gsubfile.lua" /source2;
+options lrecl=&optval;
+
%mend ml_gsubfile;
diff --git a/lua/ml_json.sas b/lua/ml_json.sas
index 08f0bfb..f501520 100644
--- a/lua/ml_json.sas
+++ b/lua/ml_json.sas
@@ -389,6 +389,14 @@ data _null_;
put '-- JSON.LUA ENDS HERE ';
run;
+/* ensure big enough lrecl to avoid lua compilation issues */
+%local optval;
+%let optval=%sysfunc(getoption(lrecl));
+options lrecl=1024;
+
+/* execute the lua code by using a .lua extension */
%inc "%sysfunc(pathname(work))/ml_json.lua" /source2;
+options lrecl=&optval;
+
%mend ml_json;
diff --git a/meta/mm_createwebservice.sas b/meta/mm_createwebservice.sas
index adf0997..68ba65b 100644
--- a/meta/mm_createwebservice.sas
+++ b/meta/mm_createwebservice.sas
@@ -103,7 +103,7 @@ data _null_;
put ' ';
put '%if &action=OPEN %then %do; ';
put ' options nobomfile; ';
- put ' data _null_;file &jref encoding=''utf-8'' ; ';
+ put ' data _null_;file &jref encoding=''utf-8'' lrecl=200; ';
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
put ' run; ';
put '%end; ';
@@ -314,6 +314,25 @@ data _null_;
put ' run; ';
put '%end; ';
put '%mend mp_jsonout; ';
+ put ' ';
+ put '%macro mf_getuser(type=META ';
+ put ')/*/STORE SOURCE*/; ';
+ put ' %local user metavar; ';
+ put ' %if &type=OS %then %let metavar=_secureusername; ';
+ put ' %else %let metavar=_metaperson; ';
+ put ' ';
+ put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
+ put ' %else %if %symexist(&metavar) %then %do; ';
+ put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
+ put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
+ put ' /* but be sure to quote in case of usernames with commas */ ';
+ put ' %else %let user=%unquote(%scan(%quote(&&&metavar),1,@)); ';
+ put ' %end; ';
+ put ' %else %let user=&sysuserid; ';
+ put ' ';
+ put ' %quote(&user) ';
+ put ' ';
+ put '%mend mf_getuser; ';
put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL ';
put ' ,showmeta=NO ';
put '); ';
@@ -455,25 +474,6 @@ data _null_;
put '%end; ';
put ' ';
put '%mend mm_webout; ';
- put ' ';
- put '%macro mf_getuser(type=META ';
- put ')/*/STORE SOURCE*/; ';
- put ' %local user metavar; ';
- put ' %if &type=OS %then %let metavar=_secureusername; ';
- put ' %else %let metavar=_metaperson; ';
- put ' ';
- put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
- put ' %else %if %symexist(&metavar) %then %do; ';
- put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
- put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
- put ' /* but be sure to quote in case of usernames with commas */ ';
- put ' %else %let user=%unquote(%scan(%quote(&&&metavar),1,@)); ';
- put ' %end; ';
- put ' %else %let user=&sysuserid; ';
- put ' ';
- put ' %quote(&user) ';
- put ' ';
- put '%mend mf_getuser; ';
/* WEBOUT END */
put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO);';
put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing';
diff --git a/sasjs/sasjsconfig.json b/sasjs/sasjsconfig.json
index aeb6988..9b74a44 100644
--- a/sasjs/sasjsconfig.json
+++ b/sasjs/sasjsconfig.json
@@ -65,7 +65,7 @@
},
{
"name": "server",
- "serverUrl": "https://sas.analytium.co.uk:5006",
+ "serverUrl": "https://sas.analytium.co.uk:5007",
"serverType": "SASJS",
"httpsAgentOptions": {
"allowInsecureRequests": false
@@ -86,6 +86,7 @@
"server",
"viya",
"tests/sas9only",
+ "tests/serveronly",
"tests/viyaonly"
]
},
diff --git a/server/mfs_httpheader.sas b/server/mfs_httpheader.sas
index 3a4eefb..972f1b0 100644
--- a/server/mfs_httpheader.sas
+++ b/server/mfs_httpheader.sas
@@ -12,7 +12,7 @@
Usage:
- %mfs_httpheader(Content-type,application/csv)
+ %mfs_httpheader(Content-Type,application/csv)
@param [in] header_name Name of the http header to set
@param [in] header_value Value of the http header to set
@@ -28,6 +28,7 @@
%macro mfs_httpheader(header_name
,header_value
)/*/STORE SOURCE*/;
+%global sasjs_stpsrv_header_loc;
%local fref fid i;
%if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc)) ne 0 %then %do;
diff --git a/server/ms_createfile.sas b/server/ms_createfile.sas
index aa13f50..b7f2a0c 100644
--- a/server/ms_createfile.sas
+++ b/server/ms_createfile.sas
@@ -21,6 +21,7 @@
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mp_abort.sas
+ @li ms_deletefile.sas
**/
@@ -29,6 +30,9 @@
,mdebug=0
);
+/* first, delete in case it exists */
+%ms_deletefile(&driveloc,mdebug=&mdebug)
+
%local fname0 fname1 fname2 boundary fname statcd msg optval;
%let fname0=%mf_getuniquefileref();
%let fname1=%mf_getuniquefileref();
@@ -40,8 +44,8 @@
options nobomfile;
data _null_;
- file &fname0 termstr=crlf;
- infile &inref end=eof;
+ file &fname0 termstr=crlf lrecl=32767;
+ infile &inref end=eof lrecl=32767;
if _n_ = 1 then do;
put "--&boundary.";
put 'Content-Disposition: form-data; name="filePath"';
@@ -70,11 +74,11 @@ run;
%if &mdebug=1 %then %do;
data _null_;
- infile &fname0;
+ infile &fname0 lrecl=32767;
input;
put _infile_;
data _null_;
- infile &fname1;
+ infile &fname1 lrecl=32767;
input;
put _infile_;
run;
diff --git a/server/ms_createwebservice.sas b/server/ms_createwebservice.sas
new file mode 100644
index 0000000..62f113a
--- /dev/null
+++ b/server/ms_createwebservice.sas
@@ -0,0 +1,532 @@
+/**
+ @file ms_createwebservice.sas
+ @brief Create a Web-Ready Stored Program
+ @details This macro creates a Stored Program along with the necessary precode
+ to enable the %webout() macro
+
+ Usage:
+
+ %* compile macros ;
+ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
+ %inc mc;
+
+ %* parmcards lets us write to a text file from open code ;
+ filename ft15f001 temp;
+ parmcards4;
+ %webout(FETCH)
+ %* do some sas, any inputs are now already WORK tables;
+ data example1 example2;
+ set sashelp.class;
+ run;
+ %* send data back;
+ %webout(OPEN)
+ %webout(ARR,example1) * Array format, fast, suitable for large tables ;
+ %webout(OBJ,example2) * Object format, easier to work with ;
+ %webout(CLOSE)
+ ;;;;
+ %ms_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001)
+
+
+
+ For more examples of using these web services with the SASjs Adapter, see:
+ https://github.com/sasjs/adapter#readme
+
+ @param [in] path= (0) The full SASjs Drive path in which to create the service
+ @param [in] name= Stored Program name
+ @param [in] desc= The description of the service (not implemented yet)
+ @param [in] precode= Space separated list of filerefs, pointing to the code
+ that needs to be attached to the beginning of the service (optional)
+ @param [in] code= (ft15f001) Space seperated fileref(s) of the actual code to
+ be added
+ @param [in] mDebug= (0) set to 1 to show debug messages in the log
+
+ SAS Macros
+ @li ms_createfile.sas
+ @li mf_getuser.sas
+ @li mf_getuniquename.sas
+ @li mf_getuniquefileref.sas
+ @li mp_abort.sas
+
+ Related Files
+ @li ms_createwebservice.test.sas
+
+ @version 9.2
+ @author Allan Bowe
+
+**/
+
+%macro ms_createwebservice(path=0
+ ,name=initService
+ ,precode=
+ ,code=ft15f001
+ ,desc=Not currently used
+ ,mDebug=0
+)/*/STORE SOURCE*/;
+
+%local dbg;
+%if &mdebug=1 %then %do;
+ %put &sysmacroname entry vars:;
+ %put _local_;
+%end;
+%else %let dbg=*;
+
+%mp_abort(iftrue=(&syscc ge 4 )
+ ,mac=ms_createwebservice
+ ,msg=%str(syscc=&syscc on macro entry)
+)
+%mp_abort(iftrue=("&path"="0")
+ ,mac=ms_createwebservice
+ ,msg=%str(Path not provided)
+)
+
+/* remove any trailing slash */
+%if "%substr(&path,%length(&path),1)" = "/" %then
+ %let path=%substr(&path,1,%length(&path)-1);
+
+/**
+ * Add webout macro
+ * These put statements are auto generated - to change the macro, change the
+ * source (ms_webout) and run `build.py`
+ */
+%local sasjsref;
+%let sasjsref=%mf_getuniquefileref();
+data _null_;
+ file &sasjsref termstr=crlf lrecl=512;
+ put "/* Created on %sysfunc(datetime(),datetime19.) by %mf_getuser() */";
+/* WEBOUT BEGIN */
+ put ' ';
+ put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y ';
+ put ' ,engine=DATASTEP ';
+ put ' ,missing=NULL ';
+ put ' ,showmeta=NO ';
+ put ')/*/STORE SOURCE*/; ';
+ put '%local tempds colinfo fmtds i numcols; ';
+ put '%let numcols=0; ';
+ put ' ';
+ put '%if &action=OPEN %then %do; ';
+ put ' options nobomfile; ';
+ put ' data _null_;file &jref encoding=''utf-8'' lrecl=200; ';
+ put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
+ put ' run; ';
+ put '%end; ';
+ put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
+ put ' options validvarname=upcase; ';
+ put ' data _null_; file &jref encoding=''utf-8'' mod; ';
+ put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
+ put ' ';
+ put ' /* grab col defs */ ';
+ put ' proc contents noprint data=&ds ';
+ put ' out=_data_(keep=name type length format formatl formatd varnum label); ';
+ put ' run; ';
+ put ' %let colinfo=%scan(&syslast,2,.); ';
+ put ' proc sort data=&colinfo; ';
+ put ' by varnum; ';
+ put ' run; ';
+ put ' /* move meta to mac vars */ ';
+ put ' data _null_; ';
+ put ' if _n_=1 then call symputx(''numcols'',nobs,''l''); ';
+ put ' set &colinfo end=last nobs=nobs; ';
+ put ' name=upcase(name); ';
+ put ' /* fix formats */ ';
+ put ' if type=2 or type=6 then do; ';
+ put ' typelong=''char''; ';
+ put ' length fmt $49.; ';
+ put ' if format='''' then fmt=cats(''$'',length,''.''); ';
+ put ' else if formatl=0 then fmt=cats(format,''.''); ';
+ put ' else fmt=cats(format,formatl,''.''); ';
+ put ' newlen=max(formatl,length); ';
+ put ' end; ';
+ put ' else do; ';
+ put ' typelong=''num''; ';
+ put ' if format='''' then fmt=''best.''; ';
+ put ' else if formatl=0 then fmt=cats(format,''.''); ';
+ put ' else if formatd=0 then fmt=cats(format,formatl,''.''); ';
+ put ' else fmt=cats(format,formatl,''.'',formatd); ';
+ put ' /* needs to be wide, for datetimes etc */ ';
+ put ' newlen=max(length,formatl,24); ';
+ put ' end; ';
+ put ' /* 32 char unique name */ ';
+ put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); ';
+ put ' ';
+ put ' call symputx(cats(''name'',_n_),name,''l''); ';
+ put ' call symputx(cats(''newname'',_n_),newname,''l''); ';
+ put ' call symputx(cats(''len'',_n_),newlen,''l''); ';
+ put ' call symputx(cats(''length'',_n_),length,''l''); ';
+ put ' call symputx(cats(''fmt'',_n_),fmt,''l''); ';
+ put ' call symputx(cats(''type'',_n_),type,''l''); ';
+ put ' call symputx(cats(''typelong'',_n_),typelong,''l''); ';
+ put ' call symputx(cats(''label'',_n_),coalescec(label,name),''l''); ';
+ put ' run; ';
+ put ' ';
+ put ' %let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
+ put ' ';
+ put ' %if &engine=PROCJSON %then %do; ';
+ put ' %if &missing=STRING %then %do; ';
+ put ' %put &sysmacroname: Special Missings not supported in proc json.; ';
+ put ' %put &sysmacroname: Switching to DATASTEP engine; ';
+ put ' %goto datastep; ';
+ put ' %end; ';
+ put ' data &tempds;set &ds; ';
+ put ' %if &fmt=N %then format _numeric_ best32.;; ';
+ put ' /* PRETTY is necessary to avoid line truncation in large files */ ';
+ put ' proc json out=&jref pretty ';
+ put ' %if &action=ARR %then nokeys ; ';
+ put ' ;export &tempds / nosastags fmtnumeric; ';
+ put ' run; ';
+ put ' %end; ';
+ put ' %else %if &engine=DATASTEP %then %do; ';
+ put ' %datastep: ';
+ put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 ';
+ put ' %then %do; ';
+ put ' %put &sysmacroname: &ds NOT FOUND!!!; ';
+ put ' %return; ';
+ put ' %end; ';
+ put ' ';
+ put ' %if &fmt=Y %then %do; ';
+ put ' data _data_; ';
+ put ' /* rename on entry */ ';
+ put ' set &ds(rename=( ';
+ put ' %do i=1 %to &numcols; ';
+ put ' &&name&i=&&newname&i ';
+ put ' %end; ';
+ put ' )); ';
+ put ' %do i=1 %to &numcols; ';
+ put ' length &&name&i $&&len&i; ';
+ put ' %if &&typelong&i=num %then %do; ';
+ put ' &&name&i=left(put(&&newname&i,&&fmt&i)); ';
+ put ' %end; ';
+ put ' %else %do; ';
+ put ' &&name&i=put(&&newname&i,&&fmt&i); ';
+ put ' %end; ';
+ put ' drop &&newname&i; ';
+ put ' %end; ';
+ put ' if _error_ then call symputx(''syscc'',1012); ';
+ put ' run; ';
+ put ' %let fmtds=&syslast; ';
+ put ' %end; ';
+ put ' ';
+ put ' proc format; /* credit yabwon for special null removal */ ';
+ put ' value bart (default=40) ';
+ put ' %if &missing=NULL %then %do; ';
+ put ' ._ - .z = null ';
+ put ' %end; ';
+ put ' %else %do; ';
+ put ' ._ = [quote()] ';
+ put ' . = null ';
+ put ' .a - .z = [quote()] ';
+ put ' %end; ';
+ put ' other = [best.]; ';
+ put ' ';
+ put ' data &tempds; ';
+ put ' attrib _all_ label=''''; ';
+ put ' %do i=1 %to &numcols; ';
+ put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
+ put ' length &&name&i $32767; ';
+ put ' format &&name&i $32767.; ';
+ put ' %end; ';
+ put ' %end; ';
+ put ' %if &fmt=Y %then %do; ';
+ put ' set &fmtds; ';
+ put ' %end; ';
+ put ' %else %do; ';
+ put ' set &ds; ';
+ put ' %end; ';
+ put ' format _numeric_ bart.; ';
+ put ' %do i=1 %to &numcols; ';
+ put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
+ put ' if findc(&&name&i,''"\''!!''0A0D09000E0F01021011''x) then do; ';
+ put ' &&name&i=''"''!!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/\\/\\\\/'',-1,&&name&i) ';
+ put ' ))))))))))))!!''"''; ';
+ put ' end; ';
+ put ' else &&name&i=quote(cats(&&name&i)); ';
+ put ' %end; ';
+ put ' %end; ';
+ put ' run; ';
+ put ' ';
+ put ' /* write to temp loc to avoid _webout truncation ';
+ put ' - https://support.sas.com/kb/49/325.html */ ';
+ put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
+ put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod ; ';
+ put ' if _n_=1 then put "["; ';
+ put ' set &tempds; ';
+ put ' if _n_>1 then put "," @; put ';
+ put ' %if &action=ARR %then "[" ; %else "{" ; ';
+ put ' %do i=1 %to &numcols; ';
+ put ' %if &i>1 %then "," ; ';
+ put ' %if &action=OBJ %then """&&name&i"":" ; ';
+ put ' &&name&i ';
+ put ' %end; ';
+ put ' %if &action=ARR %then "]" ; %else "}" ; ; ';
+ put ' /* now write the long strings to _webout 1 byte at a time */ ';
+ put ' data _null_; ';
+ put ' length filein 8 fileid 8; ';
+ put ' filein=fopen("_sjs",''I'',1,''B''); ';
+ put ' fileid=fopen("&jref",''A'',1,''B''); ';
+ put ' rec=''20''x; ';
+ put ' do while(fread(filein)=0); ';
+ put ' rc=fget(filein,rec,1); ';
+ put ' rc=fput(fileid, rec); ';
+ put ' rc=fwrite(fileid); ';
+ put ' end; ';
+ put ' /* close out the table */ ';
+ put ' rc=fput(fileid, "]"); ';
+ put ' rc=fwrite(fileid); ';
+ put ' rc=fclose(filein); ';
+ put ' rc=fclose(fileid); ';
+ put ' run; ';
+ put ' filename _sjs clear; ';
+ put ' %end; ';
+ put ' ';
+ put ' proc sql; ';
+ put ' drop table &colinfo, &tempds; ';
+ put ' ';
+ put ' %if &showmeta=YES %then %do; ';
+ put ' data _null_; file &jref encoding=''utf-8'' mod; ';
+ put ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; ';
+ put ' do i=1 to &numcols; ';
+ put ' name=quote(trim(symget(cats(''name'',i)))); ';
+ put ' format=quote(trim(symget(cats(''fmt'',i)))); ';
+ put ' label=quote(trim(symget(cats(''label'',i)))); ';
+ put ' length=quote(trim(symget(cats(''length'',i)))); ';
+ put ' type=quote(trim(symget(cats(''typelong'',i)))); ';
+ put ' if i>1 then put "," @@; ';
+ put ' put name '':{"format":'' format '',"label":'' label ';
+ put ' '',"length":'' length '',"type":'' type ''}''; ';
+ put ' end; ';
+ put ' put ''}}''; ';
+ put ' run; ';
+ put ' %end; ';
+ put '%end; ';
+ put ' ';
+ put '%else %if &action=CLOSE %then %do; ';
+ put ' data _null_; file &jref encoding=''utf-8'' mod ; ';
+ put ' put "}"; ';
+ put ' run; ';
+ put '%end; ';
+ put '%mend mp_jsonout; ';
+ put ' ';
+ put '%macro mf_getuser(type=META ';
+ put ')/*/STORE SOURCE*/; ';
+ put ' %local user metavar; ';
+ put ' %if &type=OS %then %let metavar=_secureusername; ';
+ put ' %else %let metavar=_metaperson; ';
+ put ' ';
+ put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
+ put ' %else %if %symexist(&metavar) %then %do; ';
+ put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
+ put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
+ put ' /* but be sure to quote in case of usernames with commas */ ';
+ put ' %else %let user=%unquote(%scan(%quote(&&&metavar),1,@)); ';
+ put ' %end; ';
+ put ' %else %let user=&sysuserid; ';
+ put ' ';
+ put ' %quote(&user) ';
+ put ' ';
+ put '%mend mf_getuser; ';
+ put ' ';
+ put '%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL ';
+ put ' ,showmeta=NO ';
+ put '); ';
+ put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug ';
+ put ' sasjs_tables; ';
+ put ' ';
+ put '%local i tempds; ';
+ put '%let action=%upcase(&action); ';
+ put ' ';
+ put '%if &action=FETCH %then %do; ';
+ put ' %if %str(&_debug) ge 131 %then %do; ';
+ put ' options mprint notes mprintnest; ';
+ put ' %end; ';
+ put ' %let _webin_file_count=%eval(&_webin_file_count+0); ';
+ put ' /* now read in the data */ ';
+ put ' %do i=1 %to &_webin_file_count; ';
+ put ' %if &_webin_file_count=1 %then %do; ';
+ put ' %let _webin_fileref1=&_webin_fileref; ';
+ put ' %let _webin_name1=&_webin_name; ';
+ put ' %end; ';
+ put ' data _null_; ';
+ put ' infile &&_webin_fileref&i termstr=crlf lrecl=32767; ';
+ put ' input; ';
+ put ' call symputx(''input_statement'',_infile_); ';
+ put ' putlog "&&_webin_name&i input statement: " _infile_; ';
+ put ' stop; ';
+ put ' data &&_webin_name&i; ';
+ put ' infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'' ';
+ put ' lrecl=32767; ';
+ put ' input &input_statement; ';
+ put ' %if %str(&_debug) ge 131 %then %do; ';
+ put ' if _n_<20 then putlog _infile_; ';
+ put ' %end; ';
+ put ' run; ';
+ put ' %let sasjs_tables=&sasjs_tables &&_webin_name&i; ';
+ put ' %end; ';
+ put '%end; ';
+ put ' ';
+ put '%else %if &action=OPEN %then %do; ';
+ put ' /* fix encoding and ensure enough lrecl */ ';
+ put ' OPTIONS NOBOMFILE lrecl=32767; ';
+ put ' ';
+ put ' /* set the header */ ';
+ put ' %mfs_httpheader(Content-type,application/json) ';
+ put ' ';
+ put ' /* setup json. */ ';
+ put ' data _null_;file &fref encoding=''utf-8'' termstr=lf ; ';
+ put ' put ''{"SYSDATE" : "'' "&SYSDATE" ''"''; ';
+ put ' put '',"SYSTIME" : "'' "&SYSTIME" ''"''; ';
+ put ' run; ';
+ put ' ';
+ put '%end; ';
+ put ' ';
+ put '%else %if &action=ARR or &action=OBJ %then %do; ';
+ put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref ';
+ put ' ,engine=DATASTEP,missing=&missing,showmeta=&showmeta ';
+ put ' ) ';
+ put '%end; ';
+ put '%else %if &action=CLOSE %then %do; ';
+ put ' %if %str(&_debug) ge 131 %then %do; ';
+ put ' /* if debug mode, send back first 10 records of each work table also */ ';
+ put ' options obs=10; ';
+ put ' data;run;%let tempds=%scan(&syslast,2,.); ';
+ put ' ods output Members=&tempds; ';
+ put ' proc datasets library=WORK memtype=data; ';
+ put ' %local wtcnt;%let wtcnt=0; ';
+ put ' data _null_; ';
+ put ' set &tempds; ';
+ put ' if not (upcase(name) =:"DATA"); /* ignore temp datasets */ ';
+ put ' if not (upcase(name)=:"_DATA_"); ';
+ put ' i+1; ';
+ put ' call symputx(cats(''wt'',i),name,''l''); ';
+ put ' call symputx(''wtcnt'',i,''l''); ';
+ put ' data _null_; file &fref mod encoding=''utf-8'' termstr=lf; ';
+ put ' put ",""WORK"":{"; ';
+ put ' %do i=1 %to &wtcnt; ';
+ put ' %let wt=&&wt&i; ';
+ put ' data _null_; file &fref mod encoding=''utf-8'' termstr=lf; ';
+ put ' dsid=open("WORK.&wt",''is''); ';
+ put ' nlobs=attrn(dsid,''NLOBS''); ';
+ put ' nvars=attrn(dsid,''NVARS''); ';
+ put ' rc=close(dsid); ';
+ put ' if &i>1 then put '',''@; ';
+ put ' put " ""&wt"" : {"; ';
+ put ' put ''"nlobs":'' nlobs; ';
+ put ' put '',"nvars":'' nvars; ';
+ put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES) ';
+ put ' data _null_; file &fref mod encoding=''utf-8'' termstr=lf; ';
+ put ' put "}"; ';
+ put ' %end; ';
+ put ' data _null_; file &fref mod encoding=''utf-8'' termstr=lf; ';
+ put ' put "}"; ';
+ put ' run; ';
+ put ' %end; ';
+ put ' /* close off json */ ';
+ put ' data _null_;file &fref mod encoding=''utf-8'' termstr=lf lrecl=32767; ';
+ put ' _PROGRAM=quote(trim(resolve(symget(''_PROGRAM'')))); ';
+ put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
+ put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
+ put ' put ",""_DEBUG"" : ""&_debug"" "; ';
+ put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
+ put ' put ",""SYSCC"" : ""&syscc"" "; ';
+ put ' syserrortext=quote(cats(symget(''SYSERRORTEXT''))); ';
+ put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
+ put ' SYSHOSTINFOLONG=quote(trim(symget(''SYSHOSTINFOLONG''))); ';
+ put ' put '',"SYSHOSTINFOLONG" : '' SYSHOSTINFOLONG; ';
+ put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
+ put ' put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" "; ';
+ put ' put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" "; ';
+ put ' length SYSPROCESSNAME $512; ';
+ put ' SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME))); ';
+ put ' put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME; ';
+ put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
+ put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
+ put ' put ",""SYSSITE"" : ""&syssite"" "; ';
+ put ' put ",""SYSTCPIPHOSTNAME"" : ""&SYSTCPIPHOSTNAME"" "; ';
+ put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
+ put ' put '',"SYSVLONG" : '' sysvlong; ';
+ put ' syswarningtext=quote(cats(symget(''SYSWARNINGTEXT''))); ';
+ put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; ';
+ put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
+ put ' length autoexec $512; ';
+ put ' autoexec=quote(urlencode(trim(getoption(''autoexec'')))); ';
+ put ' put '',"AUTOEXEC" : '' autoexec; ';
+ put ' length memsize $32; ';
+ put ' memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)"; ';
+ put ' memsize=quote(cats(memsize)); ';
+ put ' put '',"MEMSIZE" : '' memsize; ';
+ put ' put "}" @; ';
+ put ' run; ';
+ put '%end; ';
+ put ' ';
+ put '%mend ms_webout; ';
+ put ' ';
+ put '%macro mfs_httpheader(header_name ';
+ put ' ,header_value ';
+ put ')/*/STORE SOURCE*/; ';
+ put '%global sasjs_stpsrv_header_loc; ';
+ put '%local fref fid i; ';
+ put ' ';
+ put '%if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc)) ne 0 %then %do; ';
+ put ' %put &=fref &=sasjs_stpsrv_header_loc; ';
+ put ' %put %str(ERR)OR: %sysfunc(sysmsg()); ';
+ put ' %return; ';
+ put '%end; ';
+ put ' ';
+ put '%let fid=%sysfunc(fopen(&fref,A)); ';
+ put ' ';
+ put '%if &fid=0 %then %do; ';
+ put ' %put %str(ERR)OR: %sysfunc(sysmsg()); ';
+ put ' %return; ';
+ put '%end; ';
+ put ' ';
+ put '%let rc=%sysfunc(fput(&fid,%str(&header_name): %str(&header_value))); ';
+ put '%let rc=%sysfunc(fwrite(&fid)); ';
+ put ' ';
+ put '%let rc=%sysfunc(fclose(&fid)); ';
+ put '%let rc=%sysfunc(filename(&fref)); ';
+ put ' ';
+ put '%mend mfs_httpheader; ';
+/* WEBOUT END */
+ put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO);';
+ put ' %ms_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing';
+ put ' ,showmeta=&showmeta';
+ put ' )';
+ put '%mend;';
+run;
+
+/* add precode and code */
+%local x fref freflist;
+%let freflist=&precode &code ;
+%do x=1 %to %sysfunc(countw(&freflist));
+ %let fref=%scan(&freflist,&x);
+ %put &sysmacroname: adding &fref;
+ data _null_;
+ file &sasjsref lrecl=3000 termstr=crlf mod;
+ infile &fref lrecl=3000;
+ input;
+ put _infile_;
+ run;
+%end;
+
+/* create the web service */
+%ms_createfile(&path/&name..sas, inref=&sasjsref, mdebug=&mdebug)
+
+%put ;%put ;%put ;%put ;%put ;%put ;
+%put &sysmacroname: STP &name successfully created in &path;
+%put ;%put ;%put ;
+%put Check it out here:;
+%put ;%put ;%put ;
+%put &_sasjs_apiserverurl.&_sasjs_apipath?_PROGRAM=&path/&name;
+%put ;%put ;%put ;%put ;%put ;%put ;
+
+%mend ms_createwebservice;
diff --git a/server/ms_runstp.sas b/server/ms_runstp.sas
index e8b2107..32e4d46 100644
--- a/server/ms_runstp.sas
+++ b/server/ms_runstp.sas
@@ -15,48 +15,154 @@
parameter)
@param [in] debug= (131) The value to supply to the _debug URL parameter
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
+ @param [in] inputparams=(_null_) A dataset containing name/value pairs in the
+ following format:
+ |name:$32|value:$10000|
+ |---|---|
+ |stpmacname|some value|
+ |mustbevalidname|can be anything, oops, %abort!!|
+ @param [in] inputfiles= (_null_) A dataset containing fileref/name/filename in
+ the following format:
+ |fileref:$8|name:$32|filename:$256|
+ |---|---|--|
+ |someref|some_name|some_filename.xls|
+ |fref2|another_file|zyx_v2.csv|
+
@param [out] outref= (outweb) The output fileref to contain the response JSON
(will be created using temp engine)
+ @param [out] outlogds= (_null_) Set to the name of a dataset to contain the
+ log. Table format:
+ |line:$2000|
+ |---|
+ |log line 1|
+ |log line 2|
SAS Macros
@li mf_getuniquefileref.sas
+ @li mf_getuniquelibref.sas
@li mp_abort.sas
**/
%macro ms_runstp(pgm
,debug=131
+ ,inputparams=_null_
+ ,inputfiles=_null_
,outref=outweb
+ ,outlogds=_null_
,mdebug=0
);
-%local dbg fname1;
+%local dbg mainref authref boundary;
+%let mainref=%mf_getuniquefileref();
+%let authref=%mf_getuniquefileref();
+%let boundary=%mf_getuniquename();
+%if &inputparams=0 %then %let inputparams=_null_;
+
%if &mdebug=1 %then %do;
%put &sysmacroname entry vars:;
%put _local_;
%end;
%else %let dbg=*;
-%let fname1=%mf_getuniquefileref();
+
%mp_abort(iftrue=("&pgm"="")
,mac=&sysmacroname
,msg=%str(Program not provided)
)
+/* avoid sending bom marker to API */
+%local optval;
+%let optval=%sysfunc(getoption(bomfile));
+options nobomfile;
+
+/* add params */
data _null_;
- file &fname1 lrecl=1000;
+ file &mainref termstr=crlf lrecl=32767 mod;
+ length line $1000 name $32 value $32767;
+ if _n_=1 then call missing(of _all_);
+ set &inputparams;
+ put "--&boundary";
+ line=cats('Content-Disposition: form-data; name="',name,'"');
+ put line;
+ put ;
+ put value;
+run;
+
+/* parse input file list */
+%local webcount;
+%let webcount=0;
+data _null_;
+ set &inputfiles end=last;
+ length fileref $8 name $32 filename $256;
+ call symputx(cats('webref',_n_),fileref,'l');
+ call symputx(cats('webname',_n_),name,'l');
+ call symputx(cats('webfilename',_n_),filename,'l');
+ if last then do;
+ call symputx('webcount',_n_);
+ call missing(of _all_);
+ end;
+run;
+
+/* write out the input files */
+%local i;
+%do i=1 %to &webcount;
+ data _null_;
+ file &mainref termstr=crlf lrecl=32767 mod;
+ infile &&webref&i lrecl=32767;
+ if _n_ = 1 then do;
+ length line $32767;
+ line=cats(
+ 'Content-Disposition: form-data; name="'
+ ,"&&webname&i"
+ ,'"; filename="'
+ ,"&&webfilename&i"
+ ,'"'
+ );
+ put "--&boundary";
+ put line;
+ put "Content-Type: text/plain";
+ put ;
+ end;
+ input;
+ put _infile_; /* add the actual file to be sent */
+ run;
+%end;
+
+data _null_;
+ file &mainref termstr=crlf mod;
+ put "--&boundary--";
+run;
+
+data _null_;
+ file &authref lrecl=1000;
infile "&_sasjs_tokenfile" lrecl=1000;
input;
put 'Authorization: Bearer ' _infile_;
+ put "Content-Type: multipart/form-data; boundary=&boundary";
run;
-filename &outref temp;
+%if &mdebug=1 %then %do;
+ data _null_;
+ infile &authref;
+ input;
+ put _infile_;
+ data _null_;
+ infile &mainref;
+ input;
+ put _infile_;
+ run;
+%end;
+filename &outref temp lrecl=32767;
/* prepare request*/
-proc http method='POST' headerin=&fname1 out=&outref
+proc http method='POST' headerin=&authref in=&mainref out=&outref
url="&_sasjs_apiserverurl.&_sasjs_apipath?_program=&pgm%str(&)_debug=131";
+%if &mdebug=1 %then %do;
+ debug level=2;
+%end;
run;
%if (&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201)
- or &mdebug=1 %then %do;
+%then %do;
data _null_;infile &outref;input;putlog _infile_;run;
%end;
%mp_abort(
@@ -65,6 +171,20 @@ run;
,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
)
+/* reset options */
+options &optval;
+
+%if &outlogds ne _null_ or &mdebug=1 %then %do;
+ %local dumplib;
+ %let dumplib=%mf_getuniquelibref();
+ libname &dumplib json (&outref);
+ data &outlogds;
+ set &dumplib..log;
+ %if &mdebug=1 %then %do;
+ putlog line=;
+ %end;
+ run;
+%end;
%if &mdebug=1 %then %do;
%put &sysmacroname exit vars:;
@@ -72,6 +192,7 @@ run;
%end;
%else %do;
/* clear refs */
- filename &fname1 clear;
+ filename &authref;
+ filename &mainref;
%end;
%mend ms_runstp;
\ No newline at end of file
diff --git a/server/ms_testservice.sas b/server/ms_testservice.sas
new file mode 100644
index 0000000..16db635
--- /dev/null
+++ b/server/ms_testservice.sas
@@ -0,0 +1,152 @@
+/**
+ @file
+ @brief Will execute a SASjs web service on SASjs Server
+ @details Prepares the input files and retrieves the resulting datasets from
+ the response JSON.
+
+ @param [in] program The Stored Program endpoint to test
+ @param [in] inputfiles=(0) A list of space seperated fileref:filename pairs as
+ follows:
+ inputfiles=inref:filename inref2:filename2
+ @param [in] inputdatasets= (0) All datasets in this space seperated list are
+ converted into SASJS-formatted CSVs (see mp_ds2csv.sas) files and added to
+ the list of `inputfiles` for ingestion. The dataset will be sent with the
+ same name (no need for a colon modifier).
+ @param [in] inputparams=(0) A dataset containing name/value pairs in the
+ following format:
+ |name:$32|value:$1000|
+ |---|---|
+ |stpmacname|some value|
+ |mustbevalidname|can be anything, oops, %abort!!|
+
+ @param [in] debug= (131) Provide the _debug value to pass to the STP
+ @param [in] mdebug= (0) Set to 1 to provide macro debugging (this macro)
+ @param [out] outlib= (0) Output libref to contain the final tables. Set to
+ 0 if the service output is not in JSON format.
+ @param [out] outref= (0) Output fileref to create, to contain the full _webout
+ response.
+ @param [out] outlogds= (_null_) Set to the name of a dataset to contain the
+ log. Table format:
+ |line:$2000|
+ |---|
+ |log line 1|
+ |log line 2|
+
+ SAS Macros
+ @li mf_getuniquefileref.sas
+ @li mf_getuniquename.sas
+ @li mp_abort.sas
+ @li mp_binarycopy.sas
+ @li mp_chop.sas
+ @li mp_ds2csv.sas
+ @li ms_runstp.sas
+
+ Related Programs
+ @li mp_testservice.test.sas
+
+ @version 9.4
+ @author Allan Bowe
+
+**/
+
+%macro ms_testservice(program,
+ inputfiles=0,
+ inputdatasets=0,
+ inputparams=0,
+ debug=0,
+ mdebug=0,
+ outlib=0,
+ outref=0,
+ outlogds=_null_
+)/*/STORE SOURCE*/;
+%local dbg fref1 chopout1 chopout2;
+%if &mdebug=1 %then %do;
+ %put &sysmacroname entry vars:;
+ %put _local_;
+%end;
+%else %let dbg=*;
+
+/* convert inputdatasets to filerefs */
+%if "&inputdatasets" ne "0" %then %do;
+ %if %quote(&inputfiles)=0 %then %let inputfiles=;
+ %do i=1 %to %sysfunc(countw(&inputdatasets,%str( )));
+ %let var=%scan(&inputdatasets,&i,%str( ));
+ %local dsref&i;
+ %let dsref&i=%mf_getuniquefileref();
+ %mp_ds2csv(&var,outref=&&dsref&i,headerformat=SASJS)
+ %let inputfiles=&inputfiles &&dsref&i:%scan(&var,-1,.);
+ %end;
+%end;
+
+/* parse the filerefs - convert to a dataset */
+%let ds1=%mf_getuniquename();
+data &ds1;
+ length fileref $8 name $32 filename $256 var $300;
+ webcount=countw("&inputfiles");
+ do i=1 to webcount;
+ var=scan("&inputfiles",i,' ');
+ fileref=scan(var,1,':');
+ name=scan(var,2,':');
+ filename=cats(name,'.csv');
+ output;
+ end;
+run;
+
+
+/* execute the STP */
+%let fref1=%mf_getuniquefileref();
+
+%ms_runstp(&program
+ ,debug=&debug
+ ,inputparams=&inputparams
+ ,inputfiles=&ds1
+ ,outref=&fref1
+ ,mdebug=&mdebug
+ ,outlogds=&outlogds
+)
+
+
+/* SASjs services have the _webout embedded in wrapper JSON */
+/* Files can also be very large - so use a dedicated macro to chop it out */
+%local matchstr1 matchstr2 ;
+%let matchstr1={"status":"success","_webout":{;
+%let matchstr2=},"log":[{;
+%let chopout1=%sysfunc(pathname(work))/%mf_getuniquename(prefix=chop1);
+%let chopout2=%sysfunc(pathname(work))/%mf_getuniquename(prefix=chop2);
+
+%mp_chop("%sysfunc(pathname(&fref1,F))"
+ ,matchvar=matchstr1
+ ,keep=LAST
+ ,matchpoint=END
+ ,offset=-1
+ ,outfile="&chopout1"
+ ,mdebug=&mdebug
+)
+
+%mp_chop("&chopout1"
+ ,matchvar=matchstr2
+ ,keep=FIRST
+ ,matchpoint=START
+ ,offset=1
+ ,outfile="&chopout2"
+ ,mdebug=&mdebug
+)
+
+%if &outlib ne 0 %then %do;
+ libname &outlib json "&chopout2";
+%end;
+%if &outref ne 0 %then %do;
+ filename &outref "&chopout2";
+%end;
+
+%if &mdebug=0 %then %do;
+ filename &webref clear;
+ filename &fref1 clear;
+ filename &fref2 clear;
+%end;
+%else %do;
+ %put &sysmacroname exit vars:;
+ %put _local_;
+%end;
+
+%mend ms_testservice;
\ No newline at end of file
diff --git a/server/ms_webout.sas b/server/ms_webout.sas
index e9a31da..504ec6b 100644
--- a/server/ms_webout.sas
+++ b/server/ms_webout.sas
@@ -67,13 +67,14 @@
%let _webin_name1=&_webin_name;
%end;
data _null_;
- infile &&_webin_fileref&i termstr=crlf;
+ infile &&_webin_fileref&i termstr=crlf lrecl=32767;
input;
call symputx('input_statement',_infile_);
putlog "&&_webin_name&i input statement: " _infile_;
stop;
data &&_webin_name&i;
- infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding='utf-8';
+ infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding='utf-8'
+ lrecl=32767;
input &input_statement;
%if %str(&_debug) ge 131 %then %do;
if _n_<20 then putlog _infile_;
@@ -84,14 +85,14 @@
%end;
%else %if &action=OPEN %then %do;
- /* fix encoding */
- OPTIONS NOBOMFILE;
+ /* fix encoding and ensure enough lrecl */
+ OPTIONS NOBOMFILE lrecl=32767;
/* set the header */
%mfs_httpheader(Content-type,application/json)
- /* setup json */
- data _null_;file &fref encoding='utf-8' termstr=lf;
+ /* setup json. */
+ data _null_;file &fref encoding='utf-8' termstr=lf ;
put '{"SYSDATE" : "' "&SYSDATE" '"';
put ',"SYSTIME" : "' "&SYSTIME" '"';
run;
@@ -135,12 +136,12 @@
data _null_; file &fref mod encoding='utf-8' termstr=lf;
put "}";
%end;
- data _null_; file &fref mod encoding='utf-8' termstr=lf termstr=lf;
+ data _null_; file &fref mod encoding='utf-8' termstr=lf;
put "}";
run;
%end;
/* close off json */
- data _null_;file &fref mod encoding='utf-8' termstr=lf;
+ data _null_;file &fref mod encoding='utf-8' termstr=lf lrecl=32767;
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ",""SYSUSERID"" : ""&sysuserid"" ";
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
diff --git a/tests/crossplatform/mf_isint.test.sas b/tests/crossplatform/mf_isint.test.sas
index 7d2bfae..9759649 100644
--- a/tests/crossplatform/mf_isint.test.sas
+++ b/tests/crossplatform/mf_isint.test.sas
@@ -30,4 +30,12 @@
),
desc=Checking mf_isint(-1),
outds=work.test_results
+)
+
+%mp_assert(
+ iftrue=(
+ "%mf_isint()"="0"
+ ),
+ desc=Checking mf_isint(),
+ outds=work.test_results
)
\ No newline at end of file
diff --git a/tests/crossplatform/mp_base64copy.test.sas b/tests/crossplatform/mp_base64copy.test.sas
index ec74803..680ed35 100644
--- a/tests/crossplatform/mp_base64copy.test.sas
+++ b/tests/crossplatform/mp_base64copy.test.sas
@@ -41,7 +41,7 @@ run;
/* multibyte string check */
-filename tmp2 temp;
+filename tmp2 temp lrecl=500;
data _null_;
file tmp2;
put "'╤', '╔', '╗', '═', '╧', '╚', '╝', '║', '╟', '─', '┼', '║', '╢', '│'";
diff --git a/tests/crossplatform/mp_chop.test.sas b/tests/crossplatform/mp_chop.test.sas
new file mode 100644
index 0000000..900c21a
--- /dev/null
+++ b/tests/crossplatform/mp_chop.test.sas
@@ -0,0 +1,70 @@
+/**
+ @file
+ @brief Testing mp_chop.sas macro
+
+ SAS Macros
+ @li mp_chop.sas
+ @li mp_assert.sas
+ @li mp_assertscope.sas
+
+**/
+
+/* prep input string */
+%let src="%sysfunc(pathname(work))/file.txt";
+%let str=Chop here!;
+%let out1="%sysfunc(pathname(work))/file1.txt";
+%let out2="%sysfunc(pathname(work))/file2.txt";
+%let out3="%sysfunc(pathname(work))/file3.txt";
+%let out4="%sysfunc(pathname(work))/file4.txt";
+
+data _null_;
+ file &src;
+ put "startsection&str.endsection";
+run;
+
+
+%mp_assertscope(SNAPSHOT)
+%mp_chop(&src, matchvar=str, keep=FIRST, outfile=&out1)
+%mp_chop(&src, matchvar=str, keep=LAST, outfile=&out2)
+%mp_chop(&src, matchvar=str, keep=FIRST, matchpoint=END, outfile=&out3)
+%mp_chop(&src, matchvar=str, keep=LAST, matchpoint=END, outfile=&out4)
+%mp_assertscope(COMPARE)
+
+data _null_;
+ infile &out1 lrecl=200;
+ input;
+ call symputx('test1',_infile_);
+data _null_;
+ infile &out2 lrecl=200;
+ input;
+ call symputx('test2',_infile_);
+data _null_;
+ infile &out3 lrecl=200;
+ input;
+ call symputx('test3',_infile_);
+data _null_;
+ infile &out4 lrecl=200;
+ input;
+ call symputx('test4',_infile_);
+run;
+
+%mp_assert(
+ iftrue=("&test1" = "startsection"),
+ desc=Checking keep FIRST matchpoint START
+ outds=work.test_results
+)
+%mp_assert(
+ iftrue=("&test2" = "Chop here!endsection"),
+ desc=Checking keep LAST matchpoint START
+ outds=work.test_results
+)
+%mp_assert(
+ iftrue=("&test3" = "startsectionChop here!"),
+ desc=Checking keep FIRST matchpoint END
+ outds=work.test_results
+)
+%mp_assert(
+ iftrue=("&test4" = "endsection"),
+ desc=Checking keep LAST matchpoint END
+ outds=work.test_results
+)
diff --git a/tests/crossplatform/mp_createwebservice.test.sas b/tests/crossplatform/mp_createwebservice.test.sas
new file mode 100644
index 0000000..396d474
--- /dev/null
+++ b/tests/crossplatform/mp_createwebservice.test.sas
@@ -0,0 +1,28 @@
+/**
+ @file
+ @brief Testing ms_createwebservice.sas macro
+
+ SAS Macros
+ @li mf_getuniquefileref.sas
+ @li mp_assertscope.sas
+ @li mp_createwebservice.sas
+
+**/
+
+%let path=&mcTestAppLoc/mp_createwebservice;
+%let name=myservice;
+%let fref=%mf_getuniquefileref();
+
+data _null_;
+ file &fref lrecl=3000;
+ put '%put hello world;';
+run;
+
+%mp_assertscope(SNAPSHOT)
+%mp_createwebservice(path=&path,name=&name,code=&fref,mdebug=&sasjs_mdebug)
+%mp_assertscope(COMPARE)
+
+
+
+
+
diff --git a/tests/crossplatform/mp_ds2cards.test.sas b/tests/crossplatform/mp_ds2cards.test.sas
index e4ae3bc..e2f7f10 100644
--- a/tests/crossplatform/mp_ds2cards.test.sas
+++ b/tests/crossplatform/mp_ds2cards.test.sas
@@ -18,7 +18,7 @@
, cards_file= "%sysfunc(pathname(work))/cars.sas"
, showlog=NO
)
-%inc "%sysfunc(pathname(work))/cars.sas"/source2;
+%inc "%sysfunc(pathname(work))/cars.sas"/source2 lrecl=32767;
proc compare base=sashelp.cars compare=work.test;
quit;
@@ -48,7 +48,7 @@ run;
, append=
)
-%inc "%sysfunc(pathname(work))/c2.sas"/source2;
+%inc "%sysfunc(pathname(work))/c2.sas"/source2 lrecl=32767;
proc compare base=work.binarybase compare=work.binarycompare;
run;
diff --git a/tests/crossplatform/mp_gsubfile.test.sas b/tests/crossplatform/mp_gsubfile.test.sas
index 407f269..77edc16 100644
--- a/tests/crossplatform/mp_gsubfile.test.sas
+++ b/tests/crossplatform/mp_gsubfile.test.sas
@@ -10,7 +10,7 @@
%macro gsubtest();
-%if "%substr(&sysver,1,4)"="V.04" %then %do;
+%if "%substr(&sysver.XX,1,4)"="V.04" %then %do;
%put %str(ERR)OR: Viya 4 does not support the IO library in lua;
%return;
%end;
diff --git a/tests/crossplatform/mp_replace.test.sas b/tests/crossplatform/mp_replace.test.sas
index 2c0d1b7..2ba2d9c 100644
--- a/tests/crossplatform/mp_replace.test.sas
+++ b/tests/crossplatform/mp_replace.test.sas
@@ -39,7 +39,7 @@ run;
%let str=%str(replacewith trailing spaces );
%let rep=%str( with more spaces );
data _null_;
- file &test2;
+ file &test2 lrecl=500;
put 'blahblah';
put "blahblah&str.blah&str. replace &str.X";
put "blahbreplacewith&str.spacesahblah";
@@ -47,7 +47,7 @@ run;
%mp_replace(&test2, findvar=str, replacevar=rep)
data _null_;
- infile &test2;
+ infile &test2 lrecl=500;
input;
if _n_=2 then call symputx('test2resulta',_infile_);
if _n_=3 then call symputx('test2resultb',_infile_);
@@ -69,7 +69,7 @@ run;
%let str=%str(replace.string.with.dots );
%let rep=%str( more.dots);
data _null_;
- file &test3;
+ file &test3 lrecl=500;
put 'blahblah';
put "blahblah&str.blah&str. replace &str.X";
put "blahbreplacewith&str.spacesahblah";
@@ -77,7 +77,7 @@ run;
%mp_replace(&test3, findvar=str, replacevar=rep)
data _null_;
- infile &test3;
+ infile &test3 lrecl=500;
input;
if _n_=2 then call symputx('test3resulta',_infile_);
if _n_=3 then call symputx('test3resultb',_infile_);
diff --git a/tests/crossplatform/mp_testservice.test.sas b/tests/crossplatform/mp_testservice.test.sas
index 315326d..d52cefc 100644
--- a/tests/crossplatform/mp_testservice.test.sas
+++ b/tests/crossplatform/mp_testservice.test.sas
@@ -55,37 +55,28 @@ run;
outlib=testlib1,
outref=test1
)
-
-%global test1a test1b test1c test1d;
+%let test1=FAIL;
data _null_;
- infile test1;
- input;
- putlog _n_ _infile_;
- if _infile_=', "somedata1":' then call symputx('test1a','PASS');
- if _infile_='{"X":1 ,"Y":" t\"w\"o" ,"Z":"Z" }' then
- call symputx('test1b','PASS');
- if _infile_='], "somedata2":' then call symputx('test1c','PASS');
- if _infile_='{"X":1 ,"Y":" t\"w\"o" ,"Z":"Z" }' then
- call symputx('test1d','PASS');
+ set testlib1.somedata1;
+ if x=1 and y=' t"w"o' and z="Z" then call symputx('test1','PASS');
+ putlog (_all_)(=);
run;
+%let test2=FAIL;
+data _null_;
+ set testlib1.somedata2;
+ if x=1 and y=' t"w"o' and z="Z" then call symputx('test2','PASS');
+ putlog (_all_)(=);
+run;
+
+
%mp_assert(
- iftrue=(&test1a=PASS),
- desc=Test 1 table 1 name,
+ iftrue=(&test1=PASS),
+ desc=somedata1 created correctly,
outds=work.test_results
)
%mp_assert(
- iftrue=(&test1b=PASS),
- desc=Test 1 table 1 values,
+ iftrue=(&test2=PASS),
+ desc=somedata2 created correctly,
outds=work.test_results
)
-%mp_assert(
- iftrue=(&test1c=PASS),
- desc=Test 1 table 2 name,
- outds=work.test_results
-)
-%mp_assert(
- iftrue=(&test1d=PASS),
- desc=Test 1 table 2 values,
- outds=work.test_results
-)
\ No newline at end of file
diff --git a/tests/serveronly/mfs_httpheader.test.sas b/tests/serveronly/mfs_httpheader.test.sas
index baf5ddc..e2a2d95 100644
--- a/tests/serveronly/mfs_httpheader.test.sas
+++ b/tests/serveronly/mfs_httpheader.test.sas
@@ -9,11 +9,12 @@
**/
+%let orig_sasjs_stpsrv_header_loc=&sasjs_stpsrv_header_loc;
%let sasjs_stpsrv_header_loc=%sysfunc(pathname(work))/header.txt;
%mp_assertscope(SNAPSHOT)
-%mfs_httpheader(Content-type,application/csv)
-%mp_assertscope(COMPARE)
+%mfs_httpheader(Content-Type,application/csv)
+%mp_assertscope(COMPARE,ignorelist=sasjs_stpsrv_header_loc)
data _null_;
infile "&sasjs_stpsrv_header_loc";
@@ -27,12 +28,12 @@ run;
outds=work.test_results
)
%mp_assert(
- iftrue=("&test1"="Content-type: application/csv"),
+ iftrue=("&test1"="Content-Type: application/csv"),
desc=Checking line was created,
outds=work.test_results
)
-%mfs_httpheader(Content-type,application/text)
+%mfs_httpheader(Content-Type,application/text)
%let test2=0;
data _null_;
infile "&sasjs_stpsrv_header_loc";
@@ -46,7 +47,11 @@ run;
outds=work.test_results
)
%mp_assert(
- iftrue=("&test2"="Content-type: application/text"),
+ iftrue=("&test2"="Content-Type: application/text"),
desc=Checking line was created,
outds=work.test_results
)
+
+
+/* reset header so the test will pass */
+%let sasjs_stpsrv_header_loc=&orig_sasjs_stpsrv_header_loc;
\ No newline at end of file
diff --git a/tests/serveronly/ms_createwebservice.test.sas b/tests/serveronly/ms_createwebservice.test.sas
new file mode 100644
index 0000000..fc73251
--- /dev/null
+++ b/tests/serveronly/ms_createwebservice.test.sas
@@ -0,0 +1,28 @@
+/**
+ @file
+ @brief Testing ms_createwebservice.sas macro
+
+ SAS Macros
+ @li mf_getuniquefileref.sas
+ @li mp_assertscope.sas
+ @li ms_createwebservice.sas
+
+**/
+
+%let path=&mcTestAppLoc/ms_createwebservice;
+%let name=myservice;
+%let fref=%mf_getuniquefileref();
+
+data _null_;
+ file &fref lrecl=3000;
+ put '%put hello world;';
+run;
+
+%mp_assertscope(SNAPSHOT)
+%ms_createwebservice(path=&path,name=&name,code=&fref,mdebug=&sasjs_mdebug)
+%mp_assertscope(COMPARE)
+
+
+
+
+
diff --git a/tests/serveronly/ms_runstp.test.sas b/tests/serveronly/ms_runstp.test.sas
index 68e1e79..00fc38f 100644
--- a/tests/serveronly/ms_runstp.test.sas
+++ b/tests/serveronly/ms_runstp.test.sas
@@ -16,6 +16,7 @@ filename stpcode temp;
data _null_;
file stpcode;
put '%put hello world;';
+ put '%put _all_;';
run;
options mprint;
diff --git a/tests/serveronly/ms_testservice.test.sas b/tests/serveronly/ms_testservice.test.sas
new file mode 100644
index 0000000..e61f7dc
--- /dev/null
+++ b/tests/serveronly/ms_testservice.test.sas
@@ -0,0 +1,87 @@
+/**
+ @file
+ @brief Testing ms_testservice.sas macro
+
+ SAS Macros
+ @li ms_createwebservice.sas
+ @li ms_testservice.sas
+ @li mp_assert.sas
+
+**/
+
+filename ft15f001 temp;
+parmcards4;
+ %put Initialising sendObj: ;
+ %put _all_;
+ %webout(FETCH)
+ %webout(OPEN)
+ %macro x();
+ %if (%symexist(sasjs_tables) and %length(&sasjs_tables)>0)
+ %then %do i=1 %to %sysfunc(countw(&sasjs_tables));
+ %let table=%scan(&sasjs_tables,&i);
+ %webout(OBJ,&table,missing=STRING)
+ %end;
+ %else %do i=1 %to &_webin_file_count;
+ %webout(OBJ,&&_webin_name&i,missing=STRING)
+ %end;
+ %mend x; %x()
+ %webout(CLOSE)
+;;;;
+%put creating web service: &mcTestAppLoc/services;
+%ms_createwebservice(
+ path=&mcTestAppLoc/services,
+ name=sendObj,
+ mdebug=&sasjs_mdebug
+)
+%put created web service: &mcTestAppLoc/services;
+
+%mp_assert(
+ iftrue=(&syscc=0),
+ desc=No errors after service creation,
+ outds=work.test_results
+)
+
+/**
+ * Test 1 - send a dataset
+ */
+data work.somedata1 work.somedata2;
+ x=1;
+ y=' t"w"o';
+ z=.z;
+ label x='x factor';
+ output;
+run;
+
+%ms_testservice(&mcTestAppLoc/services/sendObj,
+ inputdatasets=work.somedata1 work.somedata2,
+ debug=log,
+ mdebug=1,
+ outlib=testlib1,
+ outref=test1
+)
+
+%let test1=FAIL;
+data _null_;
+ set testlib1.somedata1;
+ if x=1 and y=' t"w"o' and z="Z" then call symputx('test1','PASS');
+ putlog (_all_)(=);
+run;
+
+%let test2=FAIL;
+data _null_;
+ set testlib1.somedata2;
+ if x=1 and y=' t"w"o' and z="Z" then call symputx('test2','PASS');
+ putlog (_all_)(=);
+run;
+
+
+%mp_assert(
+ iftrue=(&test1=PASS),
+ desc=somedata1 created correctly,
+ outds=work.test_results
+)
+%mp_assert(
+ iftrue=(&test2=PASS),
+ desc=somedata2 created correctly,
+ outds=work.test_results
+)
diff --git a/tests/testinit.sas b/tests/testinit.sas
index c72ba4a..d9344ee 100644
--- a/tests/testinit.sas
+++ b/tests/testinit.sas
@@ -3,23 +3,27 @@
@brief init file for tests
SAS Macros
+ @li mf_uid.sas
@li mp_init.sas
- @li mv_webout.sas
+ @li ms_webout.sas
**/
/* location in metadata or SAS Drive for temporary files */
-%let mcTestAppLoc=/Public/temp/macrocore;
+%let mcTestAppLoc=/tmp/tests/sasjs/core/%mf_uid();
/* set defaults */
%mp_init()
+options lrecl=80;
+
%global _debug sasjs_mdebug;
%let sasjs_mdebug=0;
%macro loglevel();
- %if "&_debug"="2477" or "&_debug"="fields,log,trace" %then %do;
+ %if "&_debug"="2477" or "&_debug"="fields,log,trace" or "&_debug"="131"
+ %then %do;
%put debug mode activated;
options mprint mprintnest;
%let sasjs_mdebug=1;
diff --git a/tests/testterm.sas b/tests/testterm.sas
index bc0e432..1fd9136 100644
--- a/tests/testterm.sas
+++ b/tests/testterm.sas
@@ -9,7 +9,7 @@
%mp_assert(
iftrue=(&syscc=0),
- desc=Checking final error condition,
+ desc=Checking final err condition,
outds=work.test_results
)
diff --git a/viya/mv_createwebservice.sas b/viya/mv_createwebservice.sas
index 9f52d43..c2c44a0 100644
--- a/viya/mv_createwebservice.sas
+++ b/viya/mv_createwebservice.sas
@@ -247,7 +247,7 @@ data _null_;
put ' ';
put '%if &action=OPEN %then %do; ';
put ' options nobomfile; ';
- put ' data _null_;file &jref encoding=''utf-8'' ; ';
+ put ' data _null_;file &jref encoding=''utf-8'' lrecl=200; ';
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
put ' run; ';
put '%end; ';
@@ -458,6 +458,25 @@ data _null_;
put ' run; ';
put '%end; ';
put '%mend mp_jsonout; ';
+ put ' ';
+ put '%macro mf_getuser(type=META ';
+ put ')/*/STORE SOURCE*/; ';
+ put ' %local user metavar; ';
+ put ' %if &type=OS %then %let metavar=_secureusername; ';
+ put ' %else %let metavar=_metaperson; ';
+ put ' ';
+ put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
+ put ' %else %if %symexist(&metavar) %then %do; ';
+ put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
+ put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
+ put ' /* but be sure to quote in case of usernames with commas */ ';
+ put ' %else %let user=%unquote(%scan(%quote(&&&metavar),1,@)); ';
+ put ' %end; ';
+ put ' %else %let user=&sysuserid; ';
+ put ' ';
+ put ' %quote(&user) ';
+ put ' ';
+ put '%mend mf_getuser; ';
put '%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y,missing=NULL ';
put ' ,showmeta=NO ';
put '); ';
@@ -632,25 +651,6 @@ data _null_;
put '%end; ';
put ' ';
put '%mend mv_webout; ';
- put ' ';
- put '%macro mf_getuser(type=META ';
- put ')/*/STORE SOURCE*/; ';
- put ' %local user metavar; ';
- put ' %if &type=OS %then %let metavar=_secureusername; ';
- put ' %else %let metavar=_metaperson; ';
- put ' ';
- put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
- put ' %else %if %symexist(&metavar) %then %do; ';
- put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
- put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
- put ' /* but be sure to quote in case of usernames with commas */ ';
- put ' %else %let user=%unquote(%scan(%quote(&&&metavar),1,@)); ';
- put ' %end; ';
- put ' %else %let user=&sysuserid; ';
- put ' ';
- put ' %quote(&user) ';
- put ' ';
- put '%mend mf_getuser; ';
/* WEBOUT END */
put '/* if calling viya service with _job param, _program will conflict */';
put '/* so it is provided by SASjs instead as __program */';