diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 23a6c32..0000000 --- a/.editorconfig +++ /dev/null @@ -1,12 +0,0 @@ -# http://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -end_of_line = lf -insert_final_newline = false -trim_trailing_whitespace = true - - diff --git a/CHANGELOG.md b/.github/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to .github/CHANGELOG.md diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md diff --git a/SECURITY.md b/.github/SECURITY.md similarity index 100% rename from SECURITY.md rename to .github/SECURITY.md diff --git a/README.md b/README.md index 15dafd0..ac4b5a0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Macro Core [![npm package][npm-image]][npm-url] [![Github Workflow][githubworkflow-image]][githubworkflow-url] -[![Dependency Status][dependency-image]][dependency-url] [![npm](https://img.shields.io/npm/dt/@sasjs/core)]() ![Snyk Vulnerabilities for npm package](https://img.shields.io/snyk/vulnerabilities/npm/@sasjs/core) [![License](https://img.shields.io/apm/l/atomic-design-ui.svg)](/LICENSE) @@ -16,11 +15,9 @@ [npm-url]:http://npmjs.org/package/@sasjs/core [githubworkflow-image]:https://github.com/sasjs/core/actions/workflows/main.yml/badge.svg [githubworkflow-url]:https://github.com/sasjs/core/blob/main/.github/workflows/main.yml -[dependency-image]:https://david-dm.org/sasjs/core.svg [dependency-url]:https://github.com/sasjs/core/blob/main/package.json - Much quality. Many standards. The **Macro Core** library exists to save time and development effort! Herein ye shall find a veritable host of MIT-licenced, production quality SAS macros. These are a mix of tools, utilities, functions and code generators that are useful in the context of [Application Development](https://sasapps.io) on the SAS platform (eg https://datacontroller.io). [Contributions](https://github.com/sasjs/core/blob/main/CONTRIBUTING.md) are welcomed. You can download and compile them all in just two lines of SAS code: @@ -32,49 +29,61 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; Documentation: https://core.sasjs.io -# Components +## Components -**base** library (SAS9/Viya) +### **base** library (SAS9/Viya) - OS independent - Not metadata aware - No X command - Prefixes: _mf_, _mp_ -**fcmp** library (SAS9/Viya) +#### **fcmp** library (SAS9/Viya) - Function and macro names are identical, except for special cases - Prefixes: _mcf_ The fcmp macros are used to generate fcmp functions, and can be used with or without the `proc fcmp` wrapper. -**meta** library (SAS9 only) +### **meta** library (SAS9 only) + +Macros used in SAS EBI, which connect to the metadata server. - OS independent - Metadata aware - No X command - Prefixes: _mm_ -**viya** library (Viya only) +### **server** library (@sasjs/server only) +These macros are used for building applications using [@sasjs/server](https://server.sasjs.io) - an open source REST API for Desktop SAS. + +- OS independent +- @sasjs/server aware +- No X command +- Prefixes: _ms_ + +### **viya** library (Viya only) + +Macros used for interfacing with SAS Viya. - OS independent - No X command -- Prefixes: _mv_ +- Prefixes: _mv_, _mvf_ -**metax** library (SAS9 only) +### **metax** library (SAS9 only) - OS specific - Metadata aware - X command enabled - Prefixes: _mmw_,_mmu_,_mmx_ -**lua** library +### **lua** library Wait - this is a macro library - what is LUA doing here? Well, it is a little known fact that you CAN run LUA within a SAS Macro. It has to be written to a text file with a `.lua` extension, from where you can `%include` it. So, without using the `proc lua` wrapper. To contribute, simply write your freeform LUA in the LUA folder. Then run the `build.py`, which will convert your LUA into a data step with put statements, and create the macro wrapper with a `ml_` prefix. You can then use your module in any program by running: -``` +```sas /* compile the lua module */ %ml_yourmodule() @@ -89,7 +98,7 @@ run; - X command enabled - Prefixes: _mmw_,_mmu_,_mmx_ -# Installation +## Installation First, download the repo to a location your SAS system can access. Then update your sasautos path to include the components you wish to have available, eg: @@ -107,9 +116,9 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; %inc mc; ``` -# Standards +## Standards -## File Properties +### File Properties - filenames much match macro names - filenames must be lowercase, without spaces @@ -117,19 +126,20 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; - one macro per file - prefixes: - _mf_ for macro functions (can be used in open code). - - _mp_ for macro procedures (which generate sas code) + - _ml_ for macros that are used to compile LUA modules - _mm_ for metadata macros (interface with the metadata server). - _mmx_ for macros that use metadata and are XCMD enabled + - _mp_ for macro procedures (which generate sas code) + - _ms_ for macro procedures that will only work with [@sasjs/server](https://github.com/sasjs/server) + - _mv_ for macro procedures that will only work in Viya - _mx_ for macros that are XCMD enabled - - _ml_ for macros that are used to compile LUA modules - - _mv_ for macros that will only work in Viya - follow verb-noun convention - unix style line endings (lf) - individual lines should be no more than 80 characters long - UTF-8 -## Header Properties +### Header Properties The **Macro Core** documentation is created using [doxygen](http://www.doxygen.nl). A full list of attributes can be found [here](http://www.doxygen.nl/manual/commands.html) but the following are most relevant: @@ -143,7 +153,7 @@ The **Macro Core** documentation is created using [doxygen](http://www.doxygen.n All macros must be commented in the doxygen format, to enable the [online documentation](https://core.sasjs.io). -### Dependencies +#### Dependencies SAS code can contain one of two types of dependency - SAS Macros, and SAS Includes. When compiling projects using the [SASjs CLI](https://cli.sasjs.io) the doxygen header is scanned for ` @li` items under the following headers: ```sas @@ -161,7 +171,7 @@ The CLI can then extract all the dependencies and insert as precode (SAS Macros) When contributing to this library, it is therefore important to ensure that all dependencies are listed in the header in this format. -## Coding Standards +### Coding Standards - Indentation = 2 spaces. No tabs! - no trailing white space @@ -174,7 +184,7 @@ When contributing to this library, it is therefore important to ensure that all - Avoid naming collisions! All macro variables should be local scope. Use system generated work tables where possible - eg `data ; set sashelp.class; run; data &output; set &syslast; run;` - The use of `quit;` for `proc sql` is optional unless you are looking to benefit from the timing statistics. -# General Notes +## General Notes - All macros should be compatible with SAS versions from support level B and above (so currently 9.2 and later). If an earlier version is not supported, then the macro should say as such in the header documentation, and exit gracefully (eg `%if %sysevalf(&sysver<9.3) %then %return`). @@ -186,7 +196,6 @@ If you find this library useful, please leave a [star](https://github.com/sasjs/ - ## Contributors ✨ [![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors-) diff --git a/build.py b/build.py index 94761eb..6080aac 100755 --- a/build.py +++ b/build.py @@ -84,7 +84,7 @@ options noquotelenmax; """ f = open('all.sas', "w") # r / r+ / rb / rb+ / w / wb f.write(header) -folders=['base','meta','metax','viya','lua','fcmp'] +folders=['base','meta','metax','server','viya','lua','fcmp'] for folder in folders: filenames = [fn for fn in Path('./' + folder).iterdir() if fn.match("*.sas")] filenames.sort() diff --git a/main.dox b/main.dox index 33a1b14..0da9e3b 100644 --- a/main.dox +++ b/main.dox @@ -55,7 +55,18 @@ */ - /*! \dir Tests +/*! \dir server + * \brief Macros used with [sasjs/server](https://server.sasjs.io) + * \details These macros have the following attributes: + + * OS independent + * sasjs/server aware + * No X command + * Prefixes: _ms_ + + */ + +/*! \dir tests * \brief SASjs Tests * \details These folders contain the macro tests. They are first compiled and deployed (sasjs cbd) then executed (sasjs test). @@ -72,7 +83,7 @@ */ - /*! \dir lua +/*! \dir lua * \brief Lua macros * \details These macros have the following attributes: diff --git a/make_singlefile.sh b/make_singlefile.sh deleted file mode 100755 index 3fa2851..0000000 --- a/make_singlefile.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -# Concatenate all macros into a single file - -OUTFILE='./macrocore.sas' - -cat > $OUTFILE <<'EOL' -/** - @file - @brief Auto-generated file - @details - This file contains all the macros in a single file - which means it can be - 'included' in SAS with just 2 lines of code: - - filename mc url - "https://raw.githubusercontent.com/sasjs/core/main/macrocore.sas"; - %inc mc; - - The `build.sh` file in the https://github.com/sasjs/core repo - is used to create this file. - - @author Allan Bowe -**/ -EOL - -cat base/* >> $OUTFILE -cat meta/* >> $OUTFILE -cat metax/* >> $OUTFILE -cat viya/* >> $OUTFILE diff --git a/sasjs/sasjsconfig.json b/sasjs/sasjsconfig.json index d94f3fd..e4588ef 100644 --- a/sasjs/sasjsconfig.json +++ b/sasjs/sasjsconfig.json @@ -5,6 +5,7 @@ "fcmp", "meta", "metax", + "server", "viya", "lua", "tests/crossplatform" @@ -55,6 +56,18 @@ "deployServicePack": true } }, + { + "name": "server", + "serverUrl": "https://sas.analytium.co.uk:5001", + "serverType": "SASJS", + "appLoc": "/Shared Data/temp/macrocore", + "macroFolders": [ + "tests/serveronly" + ], + "deployConfig": { + "deployServicePack": true + } + }, { "name": "docsonly", "serverType": "SAS9", diff --git a/server/ms_webout.sas b/server/ms_webout.sas new file mode 100644 index 0000000..63fd918 --- /dev/null +++ b/server/ms_webout.sas @@ -0,0 +1,170 @@ +/** + @file + @brief Send data to/from @sasjs/server + @details This macro should be added to the start of each web service, + **immediately** followed by a call to: + + %ms_webout(FETCH) + + This will read all the input data and create same-named SAS datasets in the + WORK library. You can then insert your code, and send data back using the + following syntax: + + data some datasets; * make some data ; + retain some columns; + run; + + %ms_webout(OPEN) + %ms_webout(ARR,some) * Array format, fast, suitable for large tables ; + %ms_webout(OBJ,datasets) * Object format, easier to work with ; + %ms_webout(CLOSE) + + + @param action Either FETCH, OPEN, ARR, OBJ or CLOSE + @param ds The dataset to send back to the frontend + @param dslabel= value to use instead of the real name for sending to JSON + @param fmt=(Y) Set to N to send back unformatted values + @param fref=(_webout) The fileref to which to write the JSON + +

SAS Macros

+ @li mp_jsonout.sas + @li mf_getuser.sas + +

Related Macros

+ @li mv_webout.sas + @li mm_webout.sas + + @version 9.3 + @author Allan Bowe + +**/ + +%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=Y); +%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug + sasjs_tables; + +%local i tempds; +%let action=%upcase(&action); + +%if &action=FETCH %then %do; + %if %str(&_debug) ge 131 %then %do; + options mprint notes mprintnest; + %end; + %let _webin_file_count=%eval(&_webin_file_count+0); + /* now read in the data */ + %do i=1 %to &_webin_file_count; + %if &_webin_file_count=1 %then %do; + %let _webin_fileref1=&_webin_fileref; + %let _webin_name1=&_webin_name; + %end; + data _null_; + infile &&_webin_fileref&i termstr=crlf; + 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'; + input &input_statement; + %if %str(&_debug) ge 131 %then %do; + if _n_<20 then putlog _infile_; + %end; + run; + %let sasjs_tables=&sasjs_tables &&_webin_name&i; + %end; +%end; + +%else %if &action=OPEN %then %do; + /* fix encoding */ + OPTIONS NOBOMFILE; + + /* setup json */ + data _null_;file &fref encoding='utf-8'; + %if %str(&_debug) ge 131 %then %do; + put '>>weboutBEGIN<<'; + %end; + put '{"SYSDATE" : "' "&SYSDATE" '"'; + put ',"SYSTIME" : "' "&SYSTIME" '"'; + run; + +%end; + +%else %if &action=ARR or &action=OBJ %then %do; + %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref + ,engine=&jsonengine,dbg=%str(&_debug) + ) +%end; +%else %if &action=CLOSE %then %do; + %if %str(&_debug) ge 131 %then %do; + /* if debug mode, send back first 10 records of each work table also */ + options obs=10; + data;run;%let tempds=%scan(&syslast,2,.); + ods output Members=&tempds; + proc datasets library=WORK memtype=data; + %local wtcnt;%let wtcnt=0; + data _null_; + set &tempds; + if not (upcase(name) =:"DATA"); /* ignore temp datasets */ + i+1; + call symputx('wt'!!left(i),name,'l'); + call symputx('wtcnt',i,'l'); + data _null_; file &fref mod encoding='utf-8'; + put ",""WORK"":{"; + %do i=1 %to &wtcnt; + %let wt=&&wt&i; + proc contents noprint data=&wt + out=_data_ (keep=name type length format:); + run;%let tempds=%scan(&syslast,2,.); + data _null_; file &fref mod encoding='utf-8'; + dsid=open("WORK.&wt",'is'); + nlobs=attrn(dsid,'NLOBS'); + nvars=attrn(dsid,'NVARS'); + rc=close(dsid); + if &i>1 then put ','@; + put " ""&wt"" : {"; + put '"nlobs":' nlobs; + put ',"nvars":' nvars; + %mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=&jsonengine) + %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=&jsonengine) + data _null_; file &fref mod encoding='utf-8'; + put "}"; + %end; + data _null_; file &fref mod encoding='utf-8'; + put "}"; + run; + %end; + /* close off json */ + data _null_;file &fref mod encoding='utf-8'; + _PROGRAM=quote(trim(resolve(symget('_PROGRAM')))); + put ",""SYSUSERID"" : ""&sysuserid"" "; + put ",""MF_GETUSER"" : ""%mf_getuser()"" "; + put ",""_DEBUG"" : ""&_debug"" "; + put ',"_PROGRAM" : ' _PROGRAM ; + put ",""SYSCC"" : ""&syscc"" "; + put ",""SYSERRORTEXT"" : ""&syserrortext"" "; + SYSHOSTINFOLONG=quote(trim(symget('SYSHOSTINFOLONG'))); + put ',"SYSHOSTINFOLONG" : ' SYSHOSTINFOLONG; + put ",""SYSHOSTNAME"" : ""&syshostname"" "; + put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" "; + put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" "; + put ",""SYSPROCESSNAME"" : ""&SYSPROCESSNAME"" "; + put ",""SYSJOBID"" : ""&sysjobid"" "; + put ",""SYSSCPL"" : ""&sysscpl"" "; + put ",""SYSSITE"" : ""&syssite"" "; + put ",""SYSTCPIPHOSTNAME"" : ""&SYSTCPIPHOSTNAME"" "; + sysvlong=quote(trim(symget('sysvlong'))); + put ',"SYSVLONG" : ' sysvlong; + put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; + put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" '; + autoexec=quote(trim(getoption('autoexec'))); + put ',"AUTOEXEC" : ' autoexec; + memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)"; + put ',"MEMSIZE" : ' memsize; + put "}" @; + %if %str(&_debug) ge 131 %then %do; + put '>>weboutEND<<'; + %end; + run; +%end; + +%mend ms_webout; diff --git a/tests/serveronly/ms_webout.test.sas b/tests/serveronly/ms_webout.test.sas new file mode 100644 index 0000000..34dc868 --- /dev/null +++ b/tests/serveronly/ms_webout.test.sas @@ -0,0 +1,35 @@ +/** + @file + @brief Testing ms_webout macro + +

SAS Macros

+ @li mf_getuniquefileref.sas + @li ms_webout.sas + @li mp_assert.sas + +**/ + + +%let fref=%mf_getuniquefileref(); +%global _metaperson; +data some datasets; + x=1; +run; +%ms_webout(OPEN,fref=&fref) +%ms_webout(ARR,some,fref=&fref) +%ms_webout(OBJ,datasets,fref=&fref) +%ms_webout(CLOSE,fref=&fref) + +libname test JSON (&fref); +data root; + set test.root; + call symputx('checkval',sysvlong); +run; +data alldata; + set test.alldata; +run; + +%mp_assert( + iftrue=(%str(&checkval)=%str(&sysvlong)), + desc=Check if the sysvlong value was created +) \ No newline at end of file