mirror of
https://github.com/yabwon/SAS_PACKAGES.git
synced 2026-06-16 14:50:21 +00:00
75a8b77406
SAS Packages Framework, version `20260615` Changes: - Documentation update. Answer to issue: https://github.com/yabwon/SAS_PACKAGES/issues/136
483 lines
19 KiB
SAS
483 lines
19 KiB
SAS
/*+headerPackage+*/
|
|
/**############################################################################**/
|
|
/* */
|
|
/* Copyright Bartosz Jablonski, since July 2019 onward. */
|
|
/* */
|
|
/* Code is free and open source. If you want - you can use it. */
|
|
/* I tested it the best I could */
|
|
/* but it comes with absolutely no warranty whatsoever. */
|
|
/* If you cause any damage or something - it will be your own fault. */
|
|
/* You have been warned! You are using it on your own risk. */
|
|
/* However, if you decide to use it do not forget to mention author: */
|
|
/* Bartosz Jablonski (yabwon@gmail.com) */
|
|
/* */
|
|
/* Here is the official version: */
|
|
/*
|
|
Copyright (c) 2019 - 2026 Bartosz Jablonski (yabwon@gmail.com)
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included
|
|
in all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
*/
|
|
/**#############################################################################**/
|
|
|
|
/*** HELP START ***/
|
|
/* SPF (SAS Packages Framework) is a set of macros:
|
|
- to install,
|
|
- to load,
|
|
- to get help,
|
|
- to unload, or
|
|
- to generate SAS packages.
|
|
|
|
SAS Packages Framework, version 20260615.
|
|
See examples below.
|
|
|
|
A SAS package is a zip file containing a group of files
|
|
with SAS code (macros, functions, data steps generating
|
|
data, etc.) wrapped up together and %INCLUDEed by
|
|
a single load.sas file (also embedded inside the zip).
|
|
|
|
Contributors:
|
|
- Stu Sztukowski
|
|
LinkedIn: https://www.linkedin.com/in/statsguy/
|
|
GitHub: https://github.com/stu-code
|
|
- Ken Nakamatsu
|
|
LinkedIn: https://www.linkedin.com/in/k-nkmt
|
|
GitHub: https://github.com/k-nkmt
|
|
|
|
*/
|
|
/*** HELP END ***/
|
|
|
|
/*+splitCodeForPackage+*/
|
|
/*** HELP START ***/
|
|
|
|
%macro splitCodeForPackage(
|
|
codeFile /* a code file to split */
|
|
,packagePath= /* location for results */
|
|
,debug=0 /* technical parameter */
|
|
,nobs=0 /* technical parameter */
|
|
)
|
|
/*** HELP END ***/
|
|
/ des = 'Utility macro to split "one big" code into multiple files for a SAS package, version 20260615. Run %splitCodeForPackage(HELP) for help info.'
|
|
;
|
|
%if (%superq(codeFile) = ) OR (%qupcase(&codeFile.) = HELP) %then
|
|
%do;
|
|
%local options_tmp ;
|
|
%let options_tmp = ls=%sysfunc(getoption(ls)) ps=%sysfunc(getoption(ps))
|
|
%sysfunc(getoption(notes)) %sysfunc(getoption(source))
|
|
msglevel=%sysfunc(getoption(msglevel))
|
|
;
|
|
options NOnotes NOsource ls=MAX ps=MAX msglevel=N;
|
|
%put ;
|
|
%put #################################################################################;
|
|
%put ### This is short help information for the `splitCodeForPackage` macro #;
|
|
%put #-------------------------------------------------------------------------------#;
|
|
%put # #;
|
|
%put # Utility macro to *split* single file with SAS package code into multiple #;
|
|
%put # files with separate snippets, version `20260615` #;
|
|
%put # #;
|
|
%put # A SAS package is a zip file containing a group #;
|
|
%put # of SAS codes (macros, functions, data steps generating #;
|
|
%put # data, etc.) wrapped up together and included by #;
|
|
%put # a single `load.sas` file (also embedded inside the zip). #;
|
|
%put # #;
|
|
%put # The `%nrstr(%%splitCodeForPackage())` macro takes a file with SAS code #;
|
|
%put # snippets surrounded by `%str(/)*##$##-code-block-start-##$## <tag spec> *%str(/)` and #;
|
|
%put # `%str(/)*##$##-code-block-end-##$## <tag spec> *%str(/)` tags and split that file into #;
|
|
%put # multiple files and directories according to a tag specification. #;
|
|
%put # #;
|
|
%put # The `<tag spec>` is a list of pairs of the form: `type(object)` that #;
|
|
%put # indicates how the file should be split. See example 1 below for details. #;
|
|
%put # #;
|
|
%put #-------------------------------------------------------------------------------#;
|
|
%put #### Parameters: #;
|
|
%put # #;
|
|
%put # 1. `codeFile=` *Required.* Name of a file containing code #;
|
|
%put # that will be split. Required and not null. #;
|
|
%put # If empty displays this help information. #;
|
|
%put # #;
|
|
%put # - `packagePath=` *Required.* Location for package files after #;
|
|
%put # splitting into separate files and directories. #;
|
|
%put # If missing or not exist then `WORK` is used. #;
|
|
%put # #;
|
|
%put # - `debug=` *Optional.* Turns on code printing for debugging. #;
|
|
%put # #;
|
|
%put # - `nobs=` *Optional.* Technical parameter with value `0`. #;
|
|
%put # Do not change. #;
|
|
%put # #;
|
|
%put #-------------------------------------------------------------------------------#;
|
|
%put # #;
|
|
%put # Visit: `https://github.com/yabwon/SAS_PACKAGES/tree/main/SPF/Documentation` #;
|
|
%put # to learn more. #;
|
|
%put # Tutorials available at: `https://github.com/yabwon/HoW-SASPackages` #;
|
|
%put # #;
|
|
%put ### Example 1 ###################################################################;
|
|
%put # #;
|
|
%put # Assume that the `myPackageCode.sas` file #;
|
|
%put # is located in the `C:/lazy/` folder and #;
|
|
%put # contain the following code and tags: #;
|
|
%put ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas;
|
|
%put ;
|
|
%put %nrstr( /)%nrstr(*##$##-code-block-start-##$## 01_macro(abc) */ );
|
|
%put %nrstr( %%)%nrstr(macro abc(); );
|
|
%put %nrstr( %%put I am "abc".; );
|
|
%put %nrstr( %%)%nrstr(mend abc; );
|
|
%put %nrstr( /)%nrstr(*##$##-code-block-end-##$## 01_macro(abc) */ );
|
|
%put ;
|
|
%put %nrstr( /)%nrstr(*##$##-code-block-start-##$## 01_macro(efg) */ );
|
|
%put %nrstr( %%)%nrstr(macro efg(); );
|
|
%put %nrstr( %%put I am "efg".; );
|
|
%put %nrstr( %%)%nrstr(mend efg; );
|
|
%put %nrstr( /)%nrstr(*##$##-code-block-end-##$## 01_macro(efg) */ );
|
|
%put ;
|
|
%put %nrstr( proc FCMP outlib=work.f.p; );
|
|
%put %nrstr( /)%nrstr(*##$##-code-block-start-##$## 02_functions(xyz) */ );
|
|
%put %nrstr( function xyz(n); );
|
|
%put %nrstr( return(n**2 + n + 1) );
|
|
%put %nrstr( endfunc; );
|
|
%put %nrstr( /)%nrstr(*##$##-code-block-end-##$## 02_functions(xyz) */ );
|
|
%put %nrstr( quit; );
|
|
%put ;
|
|
%put ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~;
|
|
%put # #;
|
|
%put # and we want results in `C:/split/` folder, we run the following: #;
|
|
%put ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas;
|
|
%put %nrstr( filename packages "C:/SAS_PACKAGES"; %%* setup a directory for packages;);
|
|
%put %nrstr( %%include packages(SPFinit.sas); %%* enable the framework; );
|
|
%put ;
|
|
%put %nrstr( %%splitCodeForPackage%( );
|
|
%put %nrstr( codeFile=C:/lazy/myPackageCode.sas );
|
|
%put %nrstr( ,packagePath=C:/split/ %) );
|
|
%put ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~;
|
|
%put # #;
|
|
%put #################################################################################;
|
|
%put ;
|
|
options &options_tmp.;
|
|
%GOTO ENDofsplitCodeForPackage;
|
|
%end;
|
|
|
|
%local options_tmp2 ;
|
|
%let options_tmp2 = ls=%sysfunc(getoption(ls)) ps=%sysfunc(getoption(ps))
|
|
%sysfunc(getoption(notes)) %sysfunc(getoption(source))
|
|
msglevel=%sysfunc(getoption(msglevel))
|
|
;
|
|
options nomprint nosymbolgen nomlogic notes source ls=MAX ps=MAX msglevel=N ;
|
|
|
|
%let debug = %sysevalf(NOT(0=%superq(debug)));
|
|
%if 1=&debug. %then
|
|
%do;
|
|
options mprint symbolgen mlogic source source2 msglevel=i;
|
|
%end;
|
|
|
|
%put NOTE- --&SYSMACRONAME.-START--;
|
|
%local rc;
|
|
%let rc = %sysfunc(doSubL(%nrstr(
|
|
options
|
|
%sysfunc(ifc(1=&debug.
|
|
,msglevel=I ls=max ps=64 notes mprint symbolgen mlogic source source2
|
|
,msglevel=N ls=max ps=64 nonotes nomprint nosymbolgen nomlogic nosource nosource2
|
|
))
|
|
;;;;
|
|
|
|
options DLcreateDir;
|
|
libname w "%sysfunc(pathname(WORK))/_splitCodeForPackage_";
|
|
filename d "%sysfunc(pathname(WORK))/_splitCodeForPackage_/dummy";
|
|
data _null_;
|
|
file d;
|
|
put "dummy";
|
|
run;
|
|
|
|
data _null_;
|
|
length codeFile $ 4096;
|
|
codeFile = symget('codeFile');
|
|
codeFile = dequote(codeFile);
|
|
|
|
if fileexist(codeFile) then
|
|
do;
|
|
codeFile = quote(strip(codeFile),"'");
|
|
call symputX("codeFile",codeFile,"L");
|
|
end;
|
|
else
|
|
do;
|
|
put "ERROR: [splitCodeForPackage] File " codeFile 'does not exist!';
|
|
call symputX("codeFile",quote(strip(pathname('d'))),"L");
|
|
end;
|
|
run;
|
|
|
|
options notes;
|
|
filename source &codeFile.;
|
|
filename source LIST;
|
|
options nonotes;
|
|
|
|
data _null_;
|
|
length packagePath work $ 4096;
|
|
work = pathname('WORK');
|
|
packagePath = coalescec(symget('packagePath'), work);
|
|
rc = fileexist(packagePath);
|
|
if NOT rc then packagePath = work;
|
|
if rc = 1 then put "INFO: " @;
|
|
else put "WARNING: " @;
|
|
put packagePath=;
|
|
call symputX('packagePath',packagePath,"L");
|
|
run;
|
|
|
|
data w.files;
|
|
stop;
|
|
run;
|
|
|
|
data _null_;
|
|
if 1 = _N_ then
|
|
do;
|
|
declare hash H(ordered:"A");
|
|
H.defineKey('token');
|
|
H.defineData('token','start','end','lineNumber');
|
|
H.defineDone();
|
|
end;
|
|
if 1 = _E_ then
|
|
do;
|
|
H.output(dataset:'w.files');
|
|
end;
|
|
|
|
infile source END=_E_;
|
|
lineNumberN+1;
|
|
input;
|
|
|
|
length line $ 4096 lineNumber $ 256;
|
|
line = left(lowcase(_infile_));
|
|
block=scan(line,1," ");
|
|
|
|
if block in (
|
|
'/*##$##-code-block-start-##$##'
|
|
'/*##$##-code-block-end-##$##'
|
|
);
|
|
|
|
if substr(block,20,1) = 's' then
|
|
do; s=1; e=0; end;
|
|
else
|
|
do; s=0; e=1; end;
|
|
|
|
i=1;
|
|
token=block;
|
|
do while(i);
|
|
i+1;
|
|
token=scan(line,i," ");
|
|
if token='*/' OR token=' ' then i=0;
|
|
else
|
|
do;
|
|
start=0; end=0;
|
|
if H.find() then
|
|
do;
|
|
start=s;
|
|
end =e;
|
|
lineNumber = cats(lineNumberN);
|
|
end;
|
|
else
|
|
do;
|
|
start+s;
|
|
end +e;
|
|
lineNumber = catx(",",lineNumber,lineNumberN);
|
|
end;
|
|
H.replace();
|
|
/*putlog token= s= e= start= end=;*/
|
|
end;
|
|
end;
|
|
run;
|
|
|
|
title;
|
|
title1 "Attention!!! Not Matching Tags!";
|
|
title2 "Verify following tags in file:";
|
|
title3 &codeFile.;
|
|
proc print data=w.files(where=(start NE end));
|
|
run;
|
|
title;
|
|
|
|
data w.files;
|
|
set w.files end=_E_ nobs=nobs;
|
|
where start=end;
|
|
length dir $ 128 code $ 32 path $ 160;
|
|
dir =coalescec(scan(token,1,'()'),'!BAD_DIRECTORY');
|
|
code=coalescec(scan(token,2,'()'),'!BAD_CODE_FILE');
|
|
if dir = '!BAD_DIRECTORY' or code = '!BAD_CODE_FILE' then
|
|
put "WARNING: Bad directory or code file name!"
|
|
/ "WARNING- Check tag: " token ;
|
|
path=cats('/',dir,'/',code,'.sas'); /* .sas */
|
|
run;
|
|
|
|
title;
|
|
title1 "List of tags with value _ALL_ for 'dir' or 'code' variable.";
|
|
title2 "Snippets tagged this way will be copied to multiple files.";
|
|
proc print data=w.files(where=(dir = '_all_' OR code = '_all_'));
|
|
run;
|
|
title;
|
|
|
|
data w.files;
|
|
if 0=nobs then
|
|
put "WARNING: No tags found in the file";
|
|
|
|
set w.files end=_E_ nobs=nobs;
|
|
where dir NE '_all_' AND code NE '_all_';
|
|
n+1;
|
|
if 1 = _E_ then
|
|
call symputX('nobs',n,"L");
|
|
run;
|
|
|
|
title;
|
|
title "List of files";
|
|
proc print data=w.files;
|
|
run;
|
|
title;
|
|
|
|
data _null_;
|
|
set w.files;
|
|
rc = libname("_",catx("/",symget('packagePath'),dir));
|
|
rc = libname("_");
|
|
run;
|
|
|
|
filename f DUMMY;
|
|
data _null_;
|
|
if 1 =_N_ then
|
|
do;
|
|
array paths[0:&nobs.] $ 128 _temporary_;
|
|
array starts[0:&nobs.] _temporary_;
|
|
array ends[0:&nobs.] _temporary_;
|
|
array write[0:&nobs.] _temporary_;
|
|
array firstLine[0:&nobs.] _temporary_;
|
|
|
|
declare hash H();
|
|
H.defineKey('token');
|
|
H.defineData('n');
|
|
H.defineDone();
|
|
|
|
do until(_E_);
|
|
set w.files end=_E_;
|
|
paths[n]=path;
|
|
starts[n]=start;
|
|
ends[n]=end;
|
|
write[n]=0;
|
|
rc=H.add();
|
|
firstLine[n]=1;
|
|
end;
|
|
_E_=.;
|
|
length packagePath $ 4096;
|
|
retain packagePath " ";
|
|
packagePath=symget('packagePath');
|
|
end;
|
|
|
|
infile source END=_E_;
|
|
input;
|
|
|
|
length line /*lineToPrint*/ $ 4096;
|
|
line = left(lowcase(_infile_));
|
|
/*lineToPrint=_infile_;*/
|
|
block=scan(line,1," ");
|
|
|
|
if block in (
|
|
'/*##$##-code-block-start-##$##'
|
|
'/*##$##-code-block-end-##$##'
|
|
) then
|
|
do;
|
|
/********************************************************/
|
|
if substr(block,20,1) = 's' then
|
|
do; s=1; e=0; end;
|
|
else
|
|
do; s=0; e=1; end;
|
|
|
|
i=1;
|
|
token=block;
|
|
do while(i);
|
|
i+1;
|
|
token=scan(line,i," ");
|
|
if token='*/' OR token=' ' then i=0; /* if it is the end of list - stop */
|
|
else if token='_all_(_all_)' then /* if this is a snippet for ALL files in a package */
|
|
do k=1 to &nobs.;
|
|
starts[k]+ -s;
|
|
ends[k] + -e;
|
|
write[k] + (s-e);
|
|
end;
|
|
else if scan(token,2,'()')='_all_' then /* if this is a snippet for ALL files in a type */
|
|
do k=1 to &nobs.;
|
|
if scan(token,1,'()')=scan(paths[k],1,'/\') then
|
|
do;
|
|
starts[k]+ -s;
|
|
ends[k] + -e;
|
|
write[k] + (s-e);
|
|
end;
|
|
end;
|
|
else if scan(token,1,'()')='_all_' then /* if this is a snippet for ALL files with the same name */
|
|
do k=1 to &nobs.;
|
|
if (scan(token,2,'()')!!'.sas')=scan(paths[k],2,'/\') then
|
|
do;
|
|
starts[k]+ -s;
|
|
ends[k] + -e;
|
|
write[k] + (s-e);
|
|
end;
|
|
end;
|
|
else /* all other "regular" cases */
|
|
do;
|
|
if 0=H.find() then
|
|
do;
|
|
starts[n]+ -s;
|
|
ends[n] + -e;
|
|
write[n] + (s-e);
|
|
select;
|
|
when(write[n]<0)
|
|
putlog "ERROR: Wrong tags order for " token=;
|
|
when(write[n]>1)
|
|
do;
|
|
putlog "WARNING: Doubled value for tag" token=;
|
|
putlog "WARNING- detected in line " _N_;
|
|
putlog "WARNING- Check also counterpart block.";
|
|
end;
|
|
otherwise;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
/********************************************************/
|
|
end;
|
|
else
|
|
do j = 1 to hbound(write);
|
|
if write[j]>0 then
|
|
do;
|
|
length fvariable $ 4096;
|
|
fvariable=catx("/",packagePath,paths[j]);
|
|
file f FILEVAR=fvariable MOD;
|
|
/*
|
|
lineToPrintLen=(lengthn(lineToPrint));
|
|
if lineToPrintLen then
|
|
put @1 lineToPrint $varying4096. lineToPrintLen;
|
|
else put;
|
|
*/
|
|
if firstLine[j] then
|
|
do;
|
|
put '/* File generated with help of SAS Packages Framework, version 20260615. */';
|
|
firstLine[j]=0;
|
|
end;
|
|
put _infile_;
|
|
end;
|
|
end;
|
|
run;
|
|
|
|
filename f clear;
|
|
libname w clear;
|
|
)));
|
|
%put NOTE- --&sysmacroname.-END--;
|
|
options &options_tmp2.;
|
|
%ENDofsplitCodeForPackage:
|
|
%mend splitCodeForPackage;
|
|
|