1
0
mirror of https://github.com/sasjs/core.git synced 2025-12-12 06:54:35 +00:00

Compare commits

...

95 Commits

Author SHA1 Message Date
Allan Bowe
2e7fcbe5b8 fix: prevening truncation of _debug in mp_abort.sas and more reliable way to fetch syswarningtext and syserrortext 2021-09-16 14:04:50 +01:00
Allan Bowe
3e7b9f8c14 chore: fixing example in header for mp_gsubfile() 2021-09-16 13:54:27 +01:00
Allan Bowe
e9189ccc06 Merge pull request #74 from sasjs/gsub
feat: adding mp_gsubfile.sas - a SAS macro that uses Lua to perform a…
2021-09-14 19:13:15 +03:00
Allan Bowe
8c00d715c2 chore: formatting 2021-09-14 16:08:30 +01:00
Allan Bowe
d47a369cdf feat: adding mp_gsubfile.sas - a SAS macro that uses Lua to perform a full text find and replace of an entire file. Not restricted by number of characters (only memory). IIncludes a test. 2021-09-14 16:07:12 +01:00
Allan Bowe
52bf6019fd Merge pull request #73 from sasjs/defaultvars
fix: adding default lengths to vars in mv_getfoldermembers to cover u…
2021-09-11 19:26:06 +03:00
Allan Bowe
25e61fd8ef chore: updating all.sas 2021-09-11 19:25:28 +03:00
Allan Bowe
3038be83a0 fix: adding default lengths to vars in mv_getfoldermembers to cover use case where no members found (and downstream process expects an empty table with those vars. Also fixing mp_webin to cover a case where native temp filerefs (starting with a #hash) are not supported. 2021-09-11 19:24:51 +03:00
Allan Bowe
6e2447c70a fix: missing dependency in mp_webin() 2021-09-11 19:00:04 +03:00
Allan Bowe
486aba84ca Merge pull request #72 from sasjs/webinnings
New mp_webin() macro and performance improvement to mf_getuniquefileref()
2021-09-08 19:51:36 +03:00
Allan Bowe
b5944181e1 chore(dependencies): bumping cli 2021-09-08 19:35:27 +03:00
Allan Bowe
3f69cf506a feat: mp_webin() macro for handling the _webin_xxx macro variables in SAS web services 2021-09-08 19:34:28 +03:00
Allan Bowe
6013897c50 feat: updating mf_getuniquefileref() to use native approach to get a unique fileref (which is 100 times faster than the old approach) 2021-09-08 15:30:51 +03:00
Allan Bowe
27cf2a2532 Merge pull request #71 from sasjs/including
fix: conditional logic around mp_abort(mode=INCLUDE) to cover case wh…
2021-09-05 20:56:40 +03:00
Allan Bowe
d096cbddeb fix: conditional logic around mp_abort(mode=INCLUDE) to cover case when no mp_include was executed 2021-09-05 20:55:44 +03:00
Allan Bowe
117503f214 Merge pull request #70 from sasjs/including
feat: new mp_include() feature for handling %includes with macros
2021-09-03 11:17:21 +03:00
Allan Bowe
5cc5fae750 chore: adding mp_abort to mp_include sas macros list 2021-09-03 11:06:12 +03:00
Allan Bowe
d93032e1a9 chore: docs 2021-09-03 10:54:21 +03:00
Allan Bowe
507557b2cb feat: new mp_include() feature for handling %includes with macros 2021-09-03 00:40:10 +03:00
Allan Bowe
ee5c3c185a chore: merge 2021-08-28 14:22:16 +03:00
Allan Bowe
e5592a2eb2 chore: avoiding the string 'ERROR:' in logs 2021-08-28 14:21:36 +03:00
Allan Bowe
59200a6e73 Merge pull request #69 from sasjs/issue68
feat: supporting postgres timestamps for mp_ds2inserts and mp_lib2inserts
2021-08-24 21:03:09 +03:00
Allan Bowe
f468f60ae1 fix: substr issue in mp_ds2inserts. Closes #68 2021-08-24 20:51:29 +03:00
Allan Bowe
9f60d827b6 feat: supporting postgres timestamps for mp_ds2inserts and mp_lib2inserts 2021-08-24 20:39:02 +03:00
Allan Bowe
5c936ddb65 Merge pull request #67 from sasjs/issue66
feat: adding APPEND option to mp_binarycopy.sas, and a new test (mp_b…
2021-08-24 14:04:25 +03:00
Allan Bowe
d0bde62594 feat: adding APPEND option to mp_binarycopy.sas, and a new test (mp_binarycopy.test.sas). Closes #66 2021-08-24 13:43:31 +03:00
Allan Bowe
ada9192337 Merge pull request #65 from sasjs/issue64
fix: increasing limit for mv_getfoldermember.sas, also adding a test,…
2021-08-20 22:50:27 +03:00
Allan Bowe
6161f588a9 fix: increasing limit for mv_getfoldermember.sas, also adding a test, and updating mf_getapploc to search _program by default (and fixing it to work with macro tests, and updating the test for that also) 2021-08-20 22:38:56 +03:00
Allan Bowe
67079d8c17 fix: adding default value for exists in mf_existfunction 2021-08-19 00:20:49 +03:00
Allan Bowe
75bd39adb0 Merge pull request #63 from sasjs/issue62
feat: adding FCMP capability
2021-08-19 00:00:15 +03:00
Allan Bowe
078bdbeecf chore: bumping devDependency (sasjs cli) 2021-08-18 23:47:17 +03:00
Allan Bowe
8ddb86785c feat: new fcmp stpsrv_header function 2021-08-18 23:45:45 +03:00
Allan Bowe
005af0ecf8 feat: new mf_existfunction macro 2021-08-18 23:36:12 +03:00
Allan Bowe
bc410a9135 chore: fixing docs for tests 2021-08-18 19:55:05 +03:00
Allan Bowe
fc8ba2e36c chore: moving files to tidy up docs 2021-08-18 19:43:38 +03:00
Allan Bowe
756441384a feat: adding first fcmp macro, mcf_string2file.sas 2021-08-18 18:35:51 +03:00
Allan Bowe
10f9eecf9e Merge pull request #60 from sasjs/vpn-fix
chore: vpn fix
2021-08-13 14:41:25 +03:00
470ebb50a7 chore: vpn fix 2021-08-13 12:57:31 +02:00
Allan Bowe
26cd5d9d31 Merge pull request #59 from Stefan-Dimitrov-Stoyanov/patch-1
Update README.md
2021-08-12 11:59:25 +03:00
Stefan-Dimitrov-Stoyanov
0b694bb878 Update README.md
Fix the missing space at the end of the first line under the Installation header:  https://github.com/sasjs/core/blob/main/README.md#installation
2021-08-11 13:34:44 +01:00
Allan Bowe
b403c02bba chore: docs for mm_createfolder 2021-08-06 15:36:55 +03:00
Allan Bowe
0b555bb31c Merge pull request #58 from sasjs/apploc
feat: new mf_getapploc macro
2021-08-04 22:04:55 +03:00
Allan Bowe
40b513a9e3 feat: new mf_getapploc macro 2021-08-04 22:00:18 +03:00
Allan Bowe
4eacf4deae Merge pull request #57 from sasjs/mf_existfref
fix: showing filerefs that exist (even when underlying does not) in m…
2021-08-03 14:32:23 +03:00
Allan Bowe
5824423c13 fix: showing filerefs that exist (even when underlying does not) in mf_existfileref, along with 3 tests 2021-08-03 14:16:52 +03:00
Allan Bowe
ce5bfd41dc chore(docs): updating header for mm_gettables 2021-08-02 10:37:01 +03:00
Allan Bowe
0c67a07e42 Merge pull request #56 from sasjs/ddlworkz
feat: new mp_lib2inserts macro.  In addition, modified mp_getddl to i…
2021-07-30 10:11:27 +03:00
Allan Bowe
187504600a chore: fixing test 2021-07-30 00:21:02 +03:00
Allan Bowe
658d67feaa chore: fixing comments 2021-07-30 00:17:44 +03:00
Allan Bowe
5207a77591 feat: new mp_lib2inserts macro. In addition, modified mp_getddl to ignore views, closing #5. Created a test, which highlighted another issue in mp_getddl (labels were being double quoted which caused macro resolution attempts when %including). Changed to single quotes. Switched 'outlib' to 'outschema' in mp_ds2inserts to harmonise with mp_getddl. Added a maxobs option, to speed up testing. 2021-07-30 00:14:29 +03:00
Allan Bowe
4456adf1dc fix: switching default flavour from BASE to SAS to be consistent with mp_getddl 2021-07-29 20:35:57 +03:00
Allan Bowe
03962c2a50 fix: for PGSQL DDL generation, ignore tables with over 1600 columns (as they are not supported in Postgres) 2021-07-29 15:45:16 +03:00
Allan Bowe
6d2fc7e265 fix: removing bug introduced to mp_getddl and adding a test to prevent regression 2021-07-29 13:02:58 +03:00
Allan Bowe
39b2e7c5f9 fix: supporting salts over 32 chars in mp_hashdataset() 2021-07-28 23:22:43 +03:00
Allan Bowe
f99adf5c3e Merge pull request #55 from sasjs/insertforpg
feat: updating mp_ds2inserts to support postgres database
2021-07-28 19:11:38 +03:00
Allan Bowe
69f8e91a2d feat: adding salt as option for mp_hashdataset 2021-07-28 17:04:41 +03:00
Allan Bowe
5b5d01993f fix: updating mp_getddl to enable append to a fileref 2021-07-28 17:01:49 +03:00
Allan Bowe
00fa464a7c feat: updating mp_ds2inserts to support postgres database 2021-07-27 11:07:12 +03:00
Allan Bowe
a5baf46233 chore: removing unnecessary proc format and generating the all.sas file 2021-07-26 22:30:02 +03:00
Allan Bowe
d63d2a4ec1 Merge pull request #54 from sasjs/mp_ds2inserts
feat: mp_ds2inserts macro
2021-07-26 22:10:44 +03:00
Allan Bowe
900f694065 chore: removing extra period 2021-07-26 21:59:48 +03:00
Allan Bowe
838324c15e chore: updating header 2021-07-26 19:06:53 +03:00
Allan Bowe
e3205ec06c feat: mp_ds2inserts macro for creating programs for inserting data (and corresponding test) 2021-07-26 19:04:49 +03:00
Allan Bowe
154a33434e chore: more contributors 2021-07-24 21:06:30 +03:00
Allan Bowe
bfa1bbaeb1 chore: all contributors update in README 2021-07-24 21:03:49 +03:00
Allan Bowe
1f0128aec4 Merge pull request #53 from sasjs/base64doublebytefix
fix: mp_base64copy.sas fixes, removed renegade % symbol and issue wit…
2021-07-18 17:16:58 +03:00
Allan Bowe
69f03f4e14 fix: mp_base64copy.sas fixes, removed renegade % symbol and issue with truncation at character 76. Added two tests, including one to test double byte encoded characters. 2021-07-18 17:05:05 +03:00
Allan Bowe
a932f321d8 Merge pull request #52 from sasjs/sasjs-cli-version-bump
fix: bump sasjs/cli version + 'prepare' support windows CMD/Powershell
2021-07-10 09:40:48 +03:00
Saad Jutt
21200c11c1 fix: bump sasjs/cli version + 'prepare' support windows CMD/Powershell 2021-07-10 03:43:58 +05:00
Allan Bowe
825c97c49c fix: switch postinstall to prepare 2021-06-30 19:50:20 +03:00
Allan Bowe
f301899269 Merge pull request #51 from sasjs/issue50
fix: setting syscc to zero to prevent error state in response.  Close…
2021-06-29 00:09:42 +03:00
Allan Bowe
fc81f62d2f fix: setting syscc to zero to prevent error state in response. Closes #50 2021-06-28 23:52:12 +03:00
Allan Bowe
93aea5ed02 Merge pull request #49 from sasjs/logfix
Context fixes on mv_jobflow and mp_testservice
2021-06-27 00:35:24 +03:00
Allan Bowe
55d4c7238a fix: updating mp_testservice.sas and mv_jobflow to use the provided context. Also updating mv_getjobresult to fetch byte by byte (as some inputs are very wide). 2021-06-27 00:22:53 +03:00
Allan Bowe
cd75bf263a fix: removing redundant parameter from mv_getjoblog 2021-06-26 21:11:26 +03:00
Allan Bowe
929a1a9974 chore: updating docs 2021-06-24 00:39:09 +03:00
Allan Bowe
7cafb4fb36 Merge pull request #48 from sasjs/base64
feat: adding mp_base64copy macro
2021-06-24 00:30:21 +03:00
Allan Bowe
a8d222a0f8 chore: automated commit 2021-06-24 00:29:54 +03:00
Allan Bowe
ac0ddf38b0 chore: automated commit 2021-06-24 00:28:41 +03:00
Allan Bowe
ecd389c935 feat: adding mp_base64copy macro 2021-06-24 00:26:41 +03:00
Allan Bowe
06a5ea06f8 Merge pull request #46 from sasjs/mendfixes
sasjs lint fix for macro name in MEND statement
2021-06-23 22:27:36 +03:00
Allan Bowe
955471ed3c Merge branch 'main' into mendfixes 2021-06-23 21:55:44 +03:00
Allan Bowe
c8d3b43b12 fix: adding lrecl to mv_createfile to support lines 1 million characters wide. Closes #47 2021-06-23 21:53:32 +03:00
Allan Bowe
3e313b06a9 fix: adding mend in python lua build 2021-06-21 17:25:58 +03:00
Allan Bowe
d7371a4505 fix: adding mend to every macro statement using sasjs lint fix 2021-06-21 17:25:01 +03:00
Allan Bowe
32a6d15c2e Merge pull request #44 from sasjs/vpn-connection
chore: added vpn connection
2021-06-21 14:35:19 +03:00
Allan Bowe
b109e7cead fix: bumping core 2021-06-21 11:27:06 +00:00
d291d3e287 chore: added vpn connection 2021-06-21 11:49:37 +02:00
Allan Bowe
5a2968e798 fix: supporting LATIN1 as well as WLATIN1 in mm_webout 2021-06-17 16:31:29 +03:00
Allan Bowe
120ad9a7da fix: supporting bell cand escape characters when creating viya jobs / services with macro 2021-06-11 00:09:16 +03:00
Allan Bowe
67a81b2690 chore: updating the docs for mm_spkexport.sas 2021-06-08 20:09:23 +03:00
Allan Bowe
506cf1812f fix: deal with dashes in sysencoding 2021-06-08 16:59:43 +03:00
Allan Bowe
8cc0eb0dd7 Merge pull request #42 from sasjs/issue41
closes #42 Issue41
2021-06-03 22:44:16 +03:00
Allan Bowe
3f49925d01 Merge pull request #40 from sasjs/dependabot/npm_and_yarn/ws-7.4.6
chore(deps): bump ws from 7.4.5 to 7.4.6
2021-06-01 17:58:49 +03:00
dependabot[bot]
c51c9c2ca9 chore(deps): bump ws from 7.4.5 to 7.4.6
Bumps [ws](https://github.com/websockets/ws) from 7.4.5 to 7.4.6.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.4.5...7.4.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-31 02:08:52 +00:00
195 changed files with 5837 additions and 1130 deletions

104
.all-contributorsrc Normal file
View File

@@ -0,0 +1,104 @@
{
"projectName": "core",
"projectOwner": "sasjs",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"commitConvention": "angular",
"contributors": [
{
"login": "allanbowe",
"name": "Allan Bowe",
"avatar_url": "https://avatars.githubusercontent.com/u/4420615?v=4",
"profile": "https://github.com/allanbowe",
"contributions": [
"business",
"code",
"content",
"doc",
"infra",
"maintenance",
"mentoring",
"question",
"review",
"test"
]
},
{
"login": "rafgag",
"name": "rafgag",
"avatar_url": "https://avatars.githubusercontent.com/u/69139928?v=4",
"profile": "https://github.com/rafgag",
"contributions": [
"code"
]
},
{
"login": "tmoody",
"name": "Trevor Moody",
"avatar_url": "https://avatars.githubusercontent.com/u/79837106?v=4",
"profile": "https://github.com/tmoody",
"contributions": [
"code"
]
},
{
"login": "krishna-acondy",
"name": "Krishna Acondy",
"avatar_url": "https://avatars.githubusercontent.com/u/2980428?v=4",
"profile": "https://krishna-acondy.io/",
"contributions": [
"code",
"infra",
"blog",
"content",
"ideas",
"video"
]
},
{
"login": "saadjutt01",
"name": "Muhammad Saad ",
"avatar_url": "https://avatars.githubusercontent.com/u/8914650?v=4",
"profile": "https://github.com/saadjutt01",
"contributions": [
"code",
"ideas"
]
},
{
"login": "YuryShkoda",
"name": "Yury Shkoda",
"avatar_url": "https://avatars.githubusercontent.com/u/25773492?v=4",
"profile": "https://www.erudicat.com/",
"contributions": [
"code",
"infra",
"video"
]
},
{
"login": "medjedovicm",
"name": "Mihajlo Medjedovic",
"avatar_url": "https://avatars.githubusercontent.com/u/18329105?v=4",
"profile": "https://github.com/medjedovicm",
"contributions": [
"infra"
]
},
{
"login": "kkchandok",
"name": "kkchandok",
"avatar_url": "https://avatars.githubusercontent.com/u/46090627?v=4",
"profile": "https://github.com/kkchandok",
"contributions": [
"ideas"
]
}
],
"contributorsPerLine": 7
}

30
.github/vpn/config.ovpn vendored Normal file
View File

@@ -0,0 +1,30 @@
cipher AES-256-CBC
setenv FORWARD_COMPATIBLE 1
client
server-poll-timeout 4
nobind
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 443 tcp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
dev tun
dev-type tun
ns-cert-type server
setenv opt tls-version-min 1.0 or-highest
reneg-sec 604800
sndbuf 0
rcvbuf 0
# NOTE: LZO commands are pushed by the Access Server at connect time.
# NOTE: The below line doesn't disable LZO.
comp-lzo no
verb 3
setenv PUSH_PEER_INFO
ca ca.crt
cert user.crt
key user.key
tls-auth tls.key 1

View File

@@ -21,6 +21,31 @@ jobs:
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Write VPN Files
run: |
echo "$CA_CRT" > .github/vpn/ca.crt
echo "$USER_CRT" > .github/vpn/user.crt
echo "$USER_KEY" > .github/vpn/user.key
echo "$TLS_KEY" > .github/vpn/tls.key
shell: bash
env:
CA_CRT: ${{ secrets.CA_CRT}}
USER_CRT: ${{ secrets.USER_CRT }}
USER_KEY: ${{ secrets.USER_KEY }}
TLS_KEY: ${{ secrets.TLS_KEY }}
- name: Install Open VPN
run: |
sudo apt install apt-transport-https
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
sudo apt-key add openvpn-repo-pkg-key.pub
sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-focal.list
sudo apt update
sudo apt install openvpn3
- name: Start Open VPN 3
run: openvpn3 session-start --config .github/vpn/config.ovpn
- name: Install Doxygen - name: Install Doxygen
run: sudo apt-get install doxygen run: sudo apt-get install doxygen

View File

@@ -2,7 +2,7 @@
"noTrailingSpaces": true, "noTrailingSpaces": true,
"noEncodedPasswords": true, "noEncodedPasswords": true,
"hasDoxygenHeader": true, "hasDoxygenHeader": true,
"hasMacroNameInMend": false, "hasMacroNameInMend": true,
"hasMacroParentheses": true, "hasMacroParentheses": true,
"noNestedMacros": false, "noNestedMacros": false,
"noSpacesInFileNames": true, "noSpacesInFileNames": true,

View File

@@ -41,6 +41,13 @@ Documentation: https://core.sasjs.io
- No X command - No X command
- Prefixes: _mf_, _mp_ - Prefixes: _mf_, _mp_
**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)
- OS independent - OS independent
@@ -84,7 +91,7 @@ run;
# 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: 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:
```sas ```sas
options insert=(sasautos="/your/path/macrocore/base"); options insert=(sasautos="/your/path/macrocore/base");
@@ -179,3 +186,34 @@ If you find this library useful, please leave a [star](https://github.com/sasjs/
## Contributors ✨
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Allan Bowe</b></sub></a><br /><a href="#business-allanbowe" title="Business development">💼</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Code">💻</a> <a href="#content-allanbowe" title="Content">🖋</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Documentation">📖</a> <a href="#infra-allanbowe" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-allanbowe" title="Maintenance">🚧</a> <a href="#mentoring-allanbowe" title="Mentoring">🧑‍🏫</a> <a href="#question-allanbowe" title="Answering Questions">💬</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3Aallanbowe" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/rafgag"><img src="https://avatars.githubusercontent.com/u/69139928?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rafgag</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=rafgag" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/tmoody"><img src="https://avatars.githubusercontent.com/u/79837106?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Trevor Moody</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=tmoody" title="Code">💻</a></td>
<td align="center"><a href="https://krishna-acondy.io/"><img src="https://avatars.githubusercontent.com/u/2980428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Krishna Acondy</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=krishna-acondy" title="Code">💻</a> <a href="#infra-krishna-acondy" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-krishna-acondy" title="Blogposts">📝</a> <a href="#content-krishna-acondy" title="Content">🖋</a> <a href="#ideas-krishna-acondy" title="Ideas, Planning, & Feedback">🤔</a> <a href="#video-krishna-acondy" title="Videos">📹</a></td>
<td align="center"><a href="https://github.com/saadjutt01"><img src="https://avatars.githubusercontent.com/u/8914650?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muhammad Saad </b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=saadjutt01" title="Code">💻</a> <a href="#ideas-saadjutt01" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://www.erudicat.com/"><img src="https://avatars.githubusercontent.com/u/25773492?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yury Shkoda</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=YuryShkoda" title="Code">💻</a> <a href="#infra-YuryShkoda" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#video-YuryShkoda" title="Videos">📹</a></td>
<td align="center"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="#infra-medjedovicm" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/kkchandok"><img src="https://avatars.githubusercontent.com/u/46090627?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kkchandok</b></sub></a><br /><a href="#ideas-kkchandok" title="Ideas, Planning, & Feedback">🤔</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

1599
all.sas

File diff suppressed because it is too large Load Diff

View File

@@ -42,6 +42,6 @@
-1 -1
%put &sysmacroname: &feature not found; %put &sysmacroname: &feature not found;
%end; %end;
%mend; %mend mf_existfeature;
/** @endcond */ /** @endcond */

View File

@@ -17,11 +17,17 @@
%macro mf_existfileref(fref %macro mf_existfileref(fref
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%if %sysfunc(fileref(&fref))=0 %then %do; %local rc;
%let rc=%sysfunc(fileref(&fref));
%if &rc=0 %then %do;
1
%end;
%else %if &rc<0 %then %do;
%put &sysmacroname: Fileref &fref exists but the underlying file does not;
1 1
%end; %end;
%else %do; %else %do;
0 0
%end; %end;
%mend; %mend mf_existfileref;

37
base/mf_existfunction.sas Normal file
View File

@@ -0,0 +1,37 @@
/**
@file
@brief Checks if a function exists
@details Returns 1 if the function exists, else 0. Note that this function
can be slow as it needs to open the sashelp.vfuncs table.
Usage:
%put %mf_existfunction(CAT);
%put %mf_existfunction(DOG);
Full credit to [Bart](https://sasensei.com/user/305) for the vfunc pointer
and the tidy approach for pure macro data set filtering.
Check out his [SAS Packages](https://github.com/yabwon/SAS_PACKAGES)
framework! Where you can find the same [function](
https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionexists-macro
).
@param [in] name (positional) - function name
@author Allan Bowe
**/
/** @cond */
%macro mf_existfunction(name
)/*/STORE SOURCE*/;
%local dsid rc exist;
%let dsid=%sysfunc(open(sashelp.vfunc(where=(fncname="%upcase(&name)"))));
%let exist=1;
%let exist=%sysfunc(fetch(&dsid, NOSET));
%let rc=%sysfunc(close(&dsid));
%sysevalf(0 = &exist)
%mend mf_existfunction;
/** @endcond */

View File

@@ -30,6 +30,6 @@
%let rc=%sysfunc(close(&dsid)); %let rc=%sysfunc(close(&dsid));
%end; %end;
%mend; %mend mf_existvar;
/** @endcond */ /** @endcond */

View File

@@ -54,6 +54,6 @@
0 0
%put Vars not found: &found; %put Vars not found: &found;
%end; %end;
%mend; %mend mf_existvarlist;
/** @endcond */ /** @endcond */

73
base/mf_getapploc.sas Normal file
View File

@@ -0,0 +1,73 @@
/**
@file
@brief Returns the appLoc from the _program variable
@details When working with SASjs apps, web services / tests / jobs are always
deployed to a root (app) location in the SAS logical folder tree.
When building apps for use in other environments, you do not necessarily know
where the backend services will be deployed. Therefore a function like this
is handy in order to dynamically figure out the appLoc, and enable other
services to be connected by a relative reference.
SASjs apps always have the same immediate substructure (one or more of the
following):
@li /data
@li /jobs
@li /services
@li /tests/jobs
@li /tests/services
@li /tests/macros
This function works by testing for the existence of any of the above in the
automatic _program variable, and returning the part to the left of it.
Usage:
%put %mf_getapploc(&_program)
%put %mf_getapploc(/some/location/services/admin/myservice);
%put %mf_getapploc(/some/location/jobs/extract/somejob/);
%put %mf_getapploc(/some/location/tests/jobs/somejob/);
@author Allan Bowe
**/
%macro mf_getapploc(pgm);
%if "&pgm"="" %then %do;
%if %symexist(_program) %then %let pgm=&_program;
%else %do;
%put &sysmacroname: No value provided and no _program variable available;
%return;
%end;
%end;
%local root;
/**
* First check we are not in the tests/macros folder (which has no subfolders)
*/
%if %index(&pgm,/tests/macros/) %then %do;
%let root=%substr(&pgm,1,%index(&pgm,/tests/macros)-1);
&root
%return;
%end;
/**
* Next, move up two levels to avoid matches on subfolder or service name
*/
%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);
%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);
%if %index(&root,/tests/) %then %do;
%let root=%substr(&root,1,%index(&root,/tests/)-1);
%end;
%else %if %index(&root,/services) %then %do;
%let root=%substr(&root,1,%index(&root,/services)-1);
%end;
%else %if %index(&root,/jobs) %then %do;
%let root=%substr(&root,1,%index(&root,/jobs)-1);
%end;
%else %put &sysmacroname: Could not find an app location from &pgm;
&root
%mend mf_getapploc ;

View File

@@ -31,4 +31,4 @@
%sysfunc(attrc(&dsid,&attr)) %sysfunc(attrc(&dsid,&attr))
%let rc=%sysfunc(close(&dsid)); %let rc=%sysfunc(close(&dsid));
%end; %end;
%mend; %mend mf_getattrc;

View File

@@ -31,4 +31,4 @@
%sysfunc(attrn(&dsid,&attr)) %sysfunc(attrn(&dsid,&attr))
%let rc=%sysfunc(close(&dsid)); %let rc=%sysfunc(close(&dsid));
%end; %end;
%mend; %mend mf_getattrn;

View File

@@ -48,6 +48,6 @@
&engine &engine
%mend; %mend mf_getengine;
/** @endcond */ /** @endcond */

View File

@@ -44,4 +44,4 @@
%sysfunc(INPUTN(&bytes, best.),sizekmg.) %sysfunc(INPUTN(&bytes, best.),sizekmg.)
%end; %end;
%mend ; %mend mf_getfilesize ;

View File

@@ -29,4 +29,4 @@
&valc &valc
%end; %end;
%else %put %str(ERR)OR: Unable to find key &key in ds &libds; %else %put %str(ERR)OR: Unable to find key &key in ds &libds;
%mend; %mend mf_getkeyvalue;

View File

@@ -62,4 +62,4 @@
%else %if &switch=VIYARESTAPI %then %do; %else %if &switch=VIYARESTAPI %then %do;
%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/) %mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)
%end; %end;
%mend; %mend mf_getplatform;

View File

@@ -50,4 +50,4 @@
&buffer &buffer
%mend; %mend mf_getquotedstr;

View File

@@ -38,6 +38,6 @@
&schema &schema
%mend; %mend mf_getschema;
/** @endcond */ /** @endcond */

View File

@@ -1,37 +1,58 @@
/** /**
@file @file
@brief Assigns and returns an unused fileref @brief Assigns and returns an unused fileref
@details @details Using the native approach for assigning filerefs fails as some
procedures (such as proc http) do not recognise the temporary names (starting
with a hash), returning a message such as:
> ERROR 22-322: Expecting a name.
This macro works by attempting a random fileref (with a prefix), seeing if it
is already assigned, and if not - returning the fileref.
If your process can accept filerefs with the hash (#) prefix, then set
`prefix=0` to revert to the native approach - which is significantly faster
when there are a lot of filerefs in a session.
Use as follows: Use as follows:
%let fileref1=%mf_getuniquefileref(); %let fileref1=%mf_getuniquefileref();
%let fileref2=%mf_getuniquefileref(); %let fileref2=%mf_getuniquefileref(prefix=0);
%put &fileref1 &fileref2; %put &fileref1 &fileref2;
which returns: which returns filerefs similar to:
> mcref0 mcref1 > _7432233 #LN00070
@param prefix= first part of fileref. Remember that filerefs can only be 8 @param [in] prefix= (_) first part of fileref. Remember that filerefs can only
characters, so a 7 letter prefix would mean that `maxtries` should be 10. be 8 characters, so a 7 letter prefix would mean `maxtries` should be 10.
@param maxtries= the last part of the libref. Provide an integer value. 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.
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
**/ **/
%macro mf_getuniquefileref(prefix=mcref,maxtries=1000); %macro mf_getuniquefileref(prefix=_,maxtries=1000);
%local x fname; %local rc fname;
%let x=0; %if &prefix=0 %then %do;
%do x=0 %to &maxtries;
%if %sysfunc(fileref(&prefix&x)) > 0 %then %do;
%let fname=&prefix&x;
%let rc=%sysfunc(filename(fname,,temp)); %let rc=%sysfunc(filename(fname,,temp));
%if &rc %then %put %sysfunc(sysmsg()); %if &rc %then %put %sysfunc(sysmsg());
&prefix&x &fname
%*put &sysmacroname: Fileref &prefix&x was assigned and returned; %end;
%else %do;
%local x len;
%let len=%eval(8-%length(&prefix));
%let x=0;
%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));
%if &rc %then %put %sysfunc(sysmsg());
&fname
%return; %return;
%end; %end;
%end; %end;
%put unable to find available fileref in range &prefix.0-&maxtries; %put unable to find available fileref after &maxtries attempts;
%mend; %end;
%mend mf_getuniquefileref;

View File

@@ -37,4 +37,4 @@
%end; %end;
%end; %end;
%put unable to find available libref in range &prefix.0-&maxtries; %put unable to find available libref in range &prefix.0-&maxtries;
%mend; %mend mf_getuniquelibref;

View File

@@ -39,4 +39,4 @@
%quote(&user) %quote(&user)
%mend; %mend mf_getuser;

View File

@@ -30,4 +30,4 @@
%trim(&&&variable) %trim(&&&variable)
%end; %end;
%mend; %mend mf_getvalue;

View File

@@ -29,4 +29,4 @@
%let rc=%sysfunc(close(&dsid)); %let rc=%sysfunc(close(&dsid));
%end; %end;
&nvars &nvars
%mend; %mend mf_getvarcount;

View File

@@ -49,4 +49,4 @@
%let rc = %sysfunc(close(&dsid)); %let rc = %sysfunc(close(&dsid));
/* Return variable format */ /* Return variable format */
&vlen &vlen
%mend; %mend mf_getVarLen;

View File

@@ -51,4 +51,4 @@ returns:
/* Return variable number */ /* Return variable number */
&vnum. &vnum.
%mend; %mend mf_getVarNum;

View File

@@ -40,4 +40,4 @@
&engine &engine
%mend; %mend mf_getxengine;

View File

@@ -24,4 +24,4 @@
%sysevalf(%superq(param)=,boolean) %sysevalf(%superq(param)=,boolean)
%mend; %mend mf_isblank;

View File

@@ -31,4 +31,4 @@
&is_directory &is_directory
%mend; %mend mf_isdir;

View File

@@ -26,4 +26,4 @@
&root &root
%end; %end;
%mend; %mend mf_loc;

View File

@@ -64,4 +64,4 @@ Usage:
%end; %end;
%end; %end;
/* exit quietly if directory did exist.*/ /* exit quietly if directory did exist.*/
%mend; %mend mf_mkdir;

View File

@@ -16,4 +16,4 @@
%if %symexist(&var) %then %do; %if %symexist(&var) %then %do;
%superq(&var) %superq(&var)
%end; %end;
%mend; %mend mf_mval;

View File

@@ -23,4 +23,4 @@
%macro mf_nobs(libds %macro mf_nobs(libds
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%mf_getattrn(&libds,NLOBS) %mf_getattrn(&libds,NLOBS)
%mend; %mend mf_nobs;

View File

@@ -47,4 +47,4 @@
&basestr &basestr
%end; %end;
%mend; %mend mf_trimstr;

View File

@@ -62,4 +62,4 @@
%else %mf_abort(mac=mf_verifymacvars,type=&mabort,msg=&abortmsg); %else %mf_abort(mac=mf_verifymacvars,type=&mabort,msg=&abortmsg);
%exit_success: %exit_success:
%mend; %mend mf_verifymacvars;

View File

@@ -50,5 +50,5 @@
&outvar &outvar
%mend; %mend mf_wordsInStr1ButNotStr2;

View File

@@ -15,24 +15,47 @@
recognise this and fetch the log of the parent session instead) recognise this and fetch the log of the parent session instead)
@li STP environments must finish cleanly to avoid the log being sent to @li STP environments must finish cleanly to avoid the log being sent to
_webout. To assist with this, we also run stpsrvset('program error', 0) _webout. To assist with this, we also run stpsrvset('program error', 0)
and set SYSCC=0. For 9.4m3 we take a unique approach - we open a macro and set SYSCC=0. We take a unique "soft abort" approach - we open a macro
but don't close it! This provides a graceful abort, EXCEPT when called but don't close it! This works everywhere EXCEPT inside a \%include inside
called within a %include within a macro (and that macro contains additional a macro. For that, we recommend you use mp_include.sas to perform the
logic). See mp_abort.test.nofix.sas for the example case. include, and then call \%mp_abort(mode=INCLUDE) from the source program (ie,
If you know of another way to gracefully abort a 9.4m3 STP session, we'd OUTSIDE of the top-parent macro).
love to hear about it!
@param mac= to contain the name of the calling macro @param mac= to contain the name of the calling macro
@param msg= message to be returned @param msg= message to be returned
@param iftrue= supply a condition under which the macro should be executed. @param iftrue= supply a condition under which the macro should be executed.
@param errds= (work.mp_abort_errds) There is no clean way to end a process
within a %include called within a macro. Furthermore, there is no way to
test if a macro is called within a %include. To handle this particular
scenario, the %include should be switched for the mp_include.sas macro.
This provides an indicator that we are running a macro within a \%include
(`_SYSINCLUDEFILEDEVICE`) and allows us to provide a dataset with the abort
values (msg, mac).
We can then run an abort cancel FILE to stop the include running, and pass
the dataset back to the calling program to run a regular \%mp_abort().
The dataset will contain the following fields:
@li iftrue (1=1)
@li msg (the message)
@li mac (the mac param)
@version 9.4M3 @param mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked for
an abort status.
Valid values:
@li REGULAR (default)
@li INCLUDE
<h4> Related Macros </h4>
@li mp_include.sas
@version 9.4
@author Allan Bowe @author Allan Bowe
@cond @cond
**/ **/
%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1) %macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)
, errds=work.mp_abort_errds
, mode=REGULAR
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%global sysprocessmode sysprocessname; %global sysprocessmode sysprocessname;
@@ -43,9 +66,44 @@
%if %length(&mac)>0 %then %put NOTE- called by &mac; %if %length(&mac)>0 %then %put NOTE- called by &mac;
%put NOTE - &msg; %put NOTE - &msg;
%if %symexist(_SYSINCLUDEFILEDEVICE) %then %do;
%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
data &errds;
iftrue='1=1';
length mac $100 msg $5000;
mac=symget('mac');
msg=symget('msg');
run;
data _null_;
abort cancel FILE;
run;
%return;
%end;
%end;
/* Stored Process Server web app context */ /* Stored Process Server web app context */
%if %symexist(_metaperson) or "&SYSPROCESSNAME "="Compute Server " %then %do; %if %symexist(_metaperson)
or "&SYSPROCESSNAME "="Compute Server "
or &mode=INCLUDE
%then %do;
options obs=max replace nosyntaxcheck mprint; options obs=max replace nosyntaxcheck mprint;
%if &mode=INCLUDE %then %do;
%if %sysfunc(exist(&errds))=1 %then %do;
data _null_;
set &errds;
call symputx('iftrue',iftrue,'l');
call symputx('mac',mac,'l');
call symputx('msg',msg,'l');
putlog (_all_)(=);
run;
%if (&iftrue)=0 %then %return;
%end;
%else %do;
%put &sysmacroname: No include errors found;
%return;
%end;
%end;
/* extract log errs / warns, if exist */ /* extract log errs / warns, if exist */
%local logloc logline; %local logloc logline;
%global logmsg; /* capture global messages */ %global logmsg; /* capture global messages */
@@ -100,7 +158,7 @@
/* send response in SASjs JSON format */ /* send response in SASjs JSON format */
data _null_; data _null_;
file _webout mod lrecl=32000 encoding='utf-8'; file _webout mod lrecl=32000 encoding='utf-8';
length msg $32767 debug $8; length msg $32767 ;
sasdatetime=datetime(); sasdatetime=datetime();
msg=cats(symget('msg'),'\n\nLog Extract:\n',symget('logmsg')); msg=cats(symget('msg'),'\n\nLog Extract:\n',symget('logmsg'));
/* escape the quotes */ /* escape the quotes */
@@ -131,11 +189,15 @@
_PROGRAM=quote(trim(resolve(symget('_PROGRAM')))); _PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ',"_PROGRAM" : ' _PROGRAM ; put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" "; put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSERRORTEXT"" : ""&syserrortext"" "; syserrortext=quote(trim(symget('syserrortext')));
put ",""SYSERRORTEXT"" : " syserrortext;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSJOBID"" : ""&sysjobid"" "; put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""SYSSITE"" : ""&syssite"" ";
sysvlong=quote(trim(symget('sysvlong'))); sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong; put ',"SYSVLONG" : ' sysvlong;
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; syswarningtext=quote(trim(symget('syswarningtext')));
put ",""SYSWARNINGTEXT"" : " syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" '; put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" ';
put "}" @; put "}" @;
if debug ge '"131"' then put '>>weboutEND<<'; if debug ge '"131"' then put '>>weboutEND<<';
@@ -149,11 +211,22 @@
rc=stpsrvset('program error', 0); rc=stpsrvset('program error', 0);
call symputx("syscc",0,"g"); call symputx("syscc",0,"g");
run; run;
%if "%substr(&sysvlong.xxxxxxxxx,1,9)" ne "9.04.01M3" %then %do; /**
%put NOTE: Ending SAS session due to:; * endsas kills 9.4m3 deployments by orphaning multibridges.
%put NOTE- &msg; * Abort variants are ungraceful (non zero return code)
endsas; * This approach lets SAS run silently until the end :-)
%end; * Caution - fails when called within a %include within a macro
* Use mp_include() to handle this.
*/
filename skip temp;
data _null_;
file skip;
put '%macro skip();';
comment '%mend skip; -> fix lint ';
put '%macro skippy();';
comment '%mend skippy; -> fix lint ';
run;
%inc skip;
%end; %end;
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do; %else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
/* endsas kills the session making it harder to fetch results */ /* endsas kills the session making it harder to fetch results */
@@ -165,27 +238,10 @@
sysuserid=symget('sysuserid'); sysuserid=symget('sysuserid');
iftrue=symget('iftrue'); iftrue=symget('iftrue');
put (_all_)(/=); put (_all_)(/=);
call symputx('syscc',0);
abort cancel nolist; abort cancel nolist;
run; run;
%end; %end;
%else %if "%substr(&sysvlong.xxxxxxxxx,1,9)" = "9.04.01M3" %then %do;
/**
* endsas kills 9.4m3 deployments by orphaning multibridges.
* Abort variants are ungraceful (non zero return code)
* This approach lets SAS run silently until the end :-)
* Caution - fails when called within a %include within a macro
* See tests/mp_abort.test.1 for an example case.
*/
filename skip temp;
data _null_;
file skip;
put '%macro skip();';
comment '%mend skip; -> fix lint ';
put '%macro skippy();';
comment '%mend skippy; -> fix lint ';
run;
%inc skip;
%end;
%else %do; %else %do;
%abort cancel; %abort cancel;
%end; %end;

View File

@@ -142,4 +142,4 @@
proc sql; proc sql;
drop table &ds; drop table &ds;
%mend; %mend mp_assertcols;

View File

@@ -144,4 +144,4 @@
proc sql; proc sql;
drop table &ds; drop table &ds;
%mend; %mend mp_assertcolvals;

117
base/mp_base64copy.sas Normal file
View File

@@ -0,0 +1,117 @@
/**
@file
@brief Convert a file to/from base64 format
@details Creates a new version of a file either encoded or decoded using
Base64. Inspired by this post by Michael Dixon:
https://support.selerity.com.au/hc/en-us/articles/223345708-Tip-SAS-and-Base64
Usage:
filename tmp temp;
data _null_;
file tmp;
put 'base ik ally';
run;
%mp_base64copy(inref=tmp, outref=myref, action=ENCODE)
data _null_;
infile myref;
input;
put _infile_;
run;
%mp_base64copy(inref=myref, outref=mynewref, action=DECODE)
data _null_;
infile mynewref;
input;
put _infile_;
run;
@param [in] inref= Fileref of the input file (should exist)
@param [out] outref= Output fileref. If it does not exist, it is created.
@param [in] action= (ENCODE) The action to take. Valid values:
@li ENCODE - Convert the file to base64 format
@li DECODE - Decode the file from base64 format
@version 9.2
@author Allan Bowe, source: https://github.com/sasjs/core
<h4> SAS Macros </h4>
@li mp_abort.sas
**/
%macro mp_base64copy(
inref=0,
outref=0,
action=ENCODE
)/*/STORE SOURCE*/;
%let inref=%upcase(&inref);
%let outref=%upcase(&outref);
%let action=%upcase(&action);
%local infound outfound;
%let infound=0;
%let outfound=0;
data _null_;
set sashelp.vextfl(where=(fileref="&inref" or fileref="&outref"));
if fileref="&inref" then call symputx('infound',1,'l');
if fileref="&outref" then call symputx('outfound',1,'l');
run;
%mp_abort(iftrue= (&infound=0)
,mac=&sysmacroname
,msg=%str(INREF &inref NOT FOUND!)
)
%mp_abort(iftrue= (&outref=0)
,mac=&sysmacroname
,msg=%str(OUTREF NOT PROVIDED!)
)
%mp_abort(iftrue= (&action ne ENCODE and &action ne DECODE)
,mac=&sysmacroname
,msg=%str(Invalid action! Should be ENCODE OR DECODE)
)
%if &outfound=0 %then %do;
filename &outref temp lrecl=2097088;
%end;
%if &action=ENCODE %then %do;
data _null_;
length b64 $ 76 line $ 57;
retain line "";
infile &inref recfm=F lrecl= 1 end=eof;
input @1 stream $char1.;
file &outref recfm=N;
substr(line,(_N_-(CEIL(_N_/57)-1)*57),1) = byte(rank(stream));
if mod(_N_,57)=0 or EOF then do;
if eof then b64=put(trim(line),$base64X76.);
else b64=put(line, $base64X76.);
put b64 + (-1) @;
line="";
end;
run;
%end;
%else %if &action=DECODE %then %do;
data _null_;
length filein 8 fileout 8;
filein = fopen("&inref",'I',4,'B');
fileout = fopen("&outref",'O',3,'B');
char= '20'x;
do while(fread(filein)=0);
length raw $4;
do i=1 to 4;
rc=fget(filein,char,1);
substr(raw,i,1)=char;
end;
rc = fput(fileout,input(raw,$base64X4.));
rc = fwrite(fileout);
end;
rc = fclose(filein);
rc = fclose(fileout);
run;
%end;
%mend mp_base64copy;

View File

@@ -9,10 +9,29 @@
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout) %mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
@param inloc full, quoted "path/and/filename.ext" of the object to be copied To append to a file, use the mode option, eg:
@param outloc full, quoted "path/and/filename.ext" of object to be created
@param inref can override default input fileref to avoid naming clash filename tmp1 temp;
@param outref an override default output fileref to avoid naming clash filename tmp2 temp;
data _null_;
file tmp1;
put 'stacking';
run;
%mp_binarycopy(inref=tmp1, outref=tmp2, mode=APPEND)
%mp_binarycopy(inref=tmp1, outref=tmp2, mode=APPEND)
@param [in] inloc quoted "path/and/filename.ext" of the file to be copied
@param [out] outloc quoted "path/and/filename.ext" of the file to be created
@param [in] inref (____in) If provided, this fileref will take precedence over
the `inloc` parameter
@param [out] outref (____in) If provided, this fileref will take precedence
over the `outloc` parameter. It must already exist!
@param [in] mode (CREATE) Valid values:
@li CREATE - Create the file (even if it already exists)
@li APPEND - Append to the file (don't overwrite)
@returns nothing @returns nothing
@version 9.2 @version 9.2
@@ -24,20 +43,29 @@
,outloc= /* full path and filename of object to be created */ ,outloc= /* full path and filename of object to be created */
,inref=____in /* override default to use own filerefs */ ,inref=____in /* override default to use own filerefs */
,outref=____out /* override default to use own filerefs */ ,outref=____out /* override default to use own filerefs */
,mode=CREATE
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local mod outmode;
%if &mode=APPEND %then %do;
%let mod=mod;
%let outmode='a';
%end;
%else %do;
%let outmode='o';
%end;
/* these IN and OUT filerefs can point to anything */ /* these IN and OUT filerefs can point to anything */
%if &inref = ____in %then %do; %if &inref = ____in %then %do;
filename &inref &inloc lrecl=1048576 ; filename &inref &inloc lrecl=1048576 ;
%end; %end;
%if &outref=____out %then %do; %if &outref=____out %then %do;
filename &outref &outloc lrecl=1048576 ; filename &outref &outloc lrecl=1048576 &mod;
%end; %end;
/* copy the file byte-for-byte */ /* copy the file byte-for-byte */
data _null_; data _null_;
length filein 8 fileid 8; length filein 8 fileid 8;
filein = fopen("&inref",'I',1,'B'); filein = fopen("&inref",'I',1,'B');
fileid = fopen("&outref",'O',1,'B'); fileid = fopen("&outref",&outmode,1,'B');
rec = '20'x; rec = '20'x;
do while(fread(filein)=0); do while(fread(filein)=0);
rc = fget(filein,rec,1); rc = fget(filein,rec,1);
@@ -53,4 +81,4 @@
%if &outref=____out %then %do; %if &outref=____out %then %do;
filename &outref clear; filename &outref clear;
%end; %end;
%mend; %mend mp_binarycopy;

View File

@@ -67,5 +67,5 @@
else put inchar $char1.; else put inchar $char1.;
end; end;
run; run;
%mend; %mend mp_cleancsv;
/** @endcond */ /** @endcond */

View File

@@ -64,4 +64,4 @@ data &outds;
output; output;
run; run;
%mend; %mend mp_createconstraints;

View File

@@ -80,4 +80,4 @@ Usage:
) )
%end; %end;
%mend; %mend mp_createwebservice;

View File

@@ -141,4 +141,4 @@ data &outds
%end; %end;
run; run;
%mend; %mend mp_csv2ds;

View File

@@ -49,4 +49,4 @@ data &outds;
end; end;
run; run;
%mend; %mend mp_deleteconstraints;

View File

@@ -167,4 +167,4 @@ run;
by filepath file_or_folder filename ext ; by filepath file_or_folder filename ext ;
run; run;
%end; %end;
%mend; %mend mp_dirlist;

View File

@@ -47,4 +47,4 @@
%end; %end;
as &outvar length=&varlen as &outvar length=&varlen
from &libds; from &libds;
%mend; %mend mp_distinctfmtvalues;

View File

@@ -251,4 +251,4 @@ quit;
%put NOTE-;%put NOTE-; %put NOTE-;%put NOTE-;
%put NOTE- %sysfunc(dequote(&cards_file.)); %put NOTE- %sysfunc(dequote(&cards_file.));
%put NOTE-;%put NOTE-; %put NOTE-;%put NOTE-;
%mend; %mend mp_ds2cards;

View File

@@ -55,4 +55,4 @@ data _null_;
run; run;
%mend; %mend mp_ds2csv;

179
base/mp_ds2inserts.sas Normal file
View File

@@ -0,0 +1,179 @@
/**
@file
@brief Export a dataset to SQL insert statements
@details Converts dataset values to SQL insert statements for use across
multiple database types.
Usage:
%mp_ds2inserts(sashelp.class,outref=myref,outds=class)
data class;
set sashelp.class;
stop;
proc sql;
%inc myref;
@param [in] ds The dataset to be exported
@param [in] maxobs= (max) The max number of inserts to create
@param [out] outref= (0) The output fileref. If it does not exist, it is
created. If it does exist, new records are APPENDED.
@param [out] schema= (0) The library (or schema) in which the target table is
located. If not provided, is ignored.
@param [out] outds= (0) The output table to load. If not provided, will
default to the table in the &ds parameter.
@param [in] flavour= (SAS) The SQL flavour to be applied to the output. Valid
options:
@li SAS (default) - suitable for regular proc sql
@li PGSQL - Used for Postgres databases
@param [in] applydttm= (YES) If YES, any columns using datetime formats will
be converted to native DB datetime literals
<h4> SAS Macros </h4>
@li mf_existfileref.sas
@li mf_getvarcount.sas
@li mf_getvarformat.sas
@li mf_getvarlist.sas
@li mf_getvartype.sas
@version 9.2
@author Allan Bowe (credit mjsq)
**/
%macro mp_ds2inserts(ds, outref=0,schema=0,outds=0,flavour=SAS,maxobs=max
,applydttm=YES
)/*/STORE SOURCE*/;
%if not %sysfunc(exist(&ds)) %then %do;
%put %str(WAR)NING: &ds does not exist;
%return;
%end;
%if not %sysfunc(exist(&ds)) %then %do;
%put %str(WAR)NING: &ds does not exist;
%return;
%end;
%if %index(&ds,.)=0 %then %let ds=WORK.&ds;
%let flavour=%upcase(&flavour);
%if &flavour ne SAS and &flavour ne PGSQL %then %do;
%put %str(WAR)NING: &flavour is not supported;
%return;
%end;
%if &outref=0 %then %do;
%put %str(WAR)NING: Please provide a fileref;
%return;
%end;
%if %mf_existfileref(&outref)=0 %then %do;
filename &outref temp lrecl=66000;
%end;
%if &schema=0 %then %let schema=;
%else %let schema=&schema..;
%if &outds=0 %then %let outds=%scan(&ds,2,.);
%local nobs;
proc sql noprint;
select count(*) into: nobs TRIMMED from &ds;
%if &nobs=0 %then %do;
data _null_;
file &outref mod;
put "/* No rows found in &ds */";
run;
%end;
%local vars;
%let vars=%mf_getvarcount(&ds);
%if &vars=0 %then %do;
data _null_;
file &outref mod;
put "/* No columns found in &schema.&ds */";
run;
%return;
%end;
%else %if &vars>1600 and &flavour=PGSQL %then %do;
data _null_;
file &fref mod;
put "/* &schema.&ds contains &vars vars */";
put "/* Postgres cannot handle tables with over 1600 vars */";
put "/* No inserts will be generated for this table */";
run;
%return;
%end;
%local varlist varlistcomma;
%let varlist=%mf_getvarlist(&ds);
%let varlistcomma=%mf_getvarlist(&ds,dlm=%str(,),quote=double);
/* next, export data */
data _null_;
file &outref mod ;
if _n_=1 then put "/* &schema.&outds (&nobs rows, &vars columns) */";
set &ds;
%if &maxobs ne max %then %do;
if _n_>&maxobs then stop;
%end;
length _____str $32767;
format _numeric_ best.;
format _character_ ;
%local i comma var vtype vfmt;
%do i=1 %to %sysfunc(countw(&varlist));
%let var=%scan(&varlist,&i);
%let vtype=%mf_getvartype(&ds,&var);
%let vfmt=%upcase(%mf_getvarformat(&ds,&var,force=1));
%if &i=1 %then %do;
%if &flavour=SAS %then %do;
put "insert into &schema.&outds set ";
put " &var="@;
%end;
%else %if &flavour=PGSQL %then %do;
_____str=cats(
"INSERT INTO &schema.&outds ("
,symget('varlistcomma')
,") VALUES ("
);
put _____str;
put " "@;
%end;
%end;
%else %do;
%if &flavour=SAS %then %do;
put " ,&var="@;
%end;
%else %if &flavour=PGSQL %then %do;
put " ,"@;
%end;
%end;
%if &vtype=N %then %do;
%if &flavour=SAS %then %do;
put &var;
%end;
%else %if &flavour=PGSQL %then %do;
if missing(&var) then put 'NULL';
%if &applydttm=YES and "%substr(&vfmt.xxxxxxxx,1,8)"="DATETIME"
%then %do;
else put "TIMESTAMP '" &var E8601DT25.6 "'";
%end;
%else %do;
else put &var;
%end;
%end;
%end;
%else %do;
_____str="'"!!trim(tranwrd(&var,"'","''"))!!"'";
put _____str;
%end;
%end;
%if &flavour=SAS %then %do;
put ';';
%end;
%else %if &flavour=PGSQL %then %do;
put ');';
%end;
if _n_=&nobs then put /;
run;
%mend mp_ds2inserts;

View File

@@ -99,4 +99,4 @@ filename &outref temp;
run; run;
%end; %end;
%mend; %mend mp_filtergenerate;

View File

@@ -78,7 +78,7 @@ run;
data &outds; data &outds;
if &sqlrc or &syscc or &syserr then do; if &sqlrc or &syscc or &syserr then do;
REASON_CD='VALIDATION_ERROR: '!! REASON_CD='VALIDATION_ERR'!!'OR: '!!
coalescec(symget('SYSERRORTEXT'),symget('SYSWARNINGTEXT')); coalescec(symget('SYSERRORTEXT'),symget('SYSWARNINGTEXT'));
output; output;
end; end;

View File

@@ -57,4 +57,4 @@ create table &outds as
%end; %end;
; ;
%mend; %mend mp_getconstraints;

View File

@@ -332,4 +332,4 @@ run;
run; run;
%end; %end;
%mend; %mend mp_getdbml;

View File

@@ -5,6 +5,8 @@
to create tables in SAS or a database. The macro can be used at table or to create tables in SAS or a database. The macro can be used at table or
library level. The default behaviour is to create DDL in SAS format. library level. The default behaviour is to create DDL in SAS format.
Note - views are not currently supported.
Usage: Usage:
data test(index=(pk=(x y)/unique /nomiss)); data test(index=(pk=(x y)/unique /nomiss));
@@ -16,12 +18,14 @@
%mp_getddl(work,test,flavour=tsql,showlog=YES) %mp_getddl(work,test,flavour=tsql,showlog=YES)
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_existfileref.sas
@li mf_getvarcount.sas
@li mp_getconstraints.sas @li mp_getconstraints.sas
@param lib libref of the library to create DDL for. Should be assigned. @param lib libref of the library to create DDL for. Should be assigned.
@param ds dataset to create ddl for (optional) @param ds dataset to create ddl for (optional)
@param fref= the fileref to which to write the DDL. If not preassigned, will @param fref= the fileref to which to _append_ the DDL. If it does not exist,
be assigned to TEMP. it will be created.
@param flavour= The type of DDL to create (default=SAS). Supported=TSQL @param flavour= The type of DDL to create (default=SAS). Supported=TSQL
@param showlog= Set to YES to show the DDL in the log @param showlog= Set to YES to show the DDL in the log
@param schema= Choose a preferred schema name (default is to use actual schema @param schema= Choose a preferred schema name (default is to use actual schema
@@ -37,9 +41,10 @@
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
/* check fileref is assigned */ /* check fileref is assigned */
%if %sysfunc(fileref(&fref)) > 0 %then %do; %if %mf_existfileref(&fref)=0 %then %do;
filename &fref temp; filename &fref temp ;
%end; %end;
%if %length(&libref)=0 %then %let libref=WORK; %if %length(&libref)=0 %then %let libref=WORK;
%let flavour=%upcase(&flavour); %let flavour=%upcase(&flavour);
@@ -47,6 +52,7 @@ proc sql noprint;
create table _data_ as create table _data_ as
select * from dictionary.tables select * from dictionary.tables
where upcase(libname)="%upcase(&libref)" where upcase(libname)="%upcase(&libref)"
and memtype='DATA' /* views not currently supported */
%if %length(&ds)>0 %then %do; %if %length(&ds)>0 %then %do;
and upcase(memname)="%upcase(&ds)" and upcase(memname)="%upcase(&ds)"
%end; %end;
@@ -115,10 +121,10 @@ create table _data_ as
end; end;
run; run;
%put &=constraints_used; %put &=constraints_used;
%mend; %mend addConst;
data _null_; data _null_;
file &fref; file &fref mod;
put "/* DDL generated by &sysuserid on %sysfunc(datetime(),datetime19.) */"; put "/* DDL generated by &sysuserid on %sysfunc(datetime(),datetime19.) */";
run; run;
@@ -141,13 +147,15 @@ run;
put "create table &libref..&curds("; put "create table &libref..&curds(";
end; end;
else do; else do;
/* just a placeholder - we filter out views at the top */
put "create view &libref..&curds("; put "create view &libref..&curds(";
end; end;
put " "@@; put " "@@;
end; end;
else put " ,"@@; else put " ,"@@;
if length(format)>1 then fmt=" format="!!cats(format); if length(format)>1 then fmt=" format="!!cats(format);
if length(label)>1 then lab=" label="!!quote(trim(label)); if length(label)>1 then
lab=" label="!!cats("'",tranwrd(label,"'","''"),"'");
if notnull='yes' then notnul=' not null'; if notnull='yes' then notnul=' not null';
if type='char' then typ=cats('char(',length,')'); if type='char' then typ=cats('char(',length,')');
else if length ne 8 then typ='num length='!!left(length); else if length ne 8 then typ='num length='!!left(length);
@@ -219,6 +227,7 @@ run;
put "create table [&schema].[&curds]("; put "create table [&schema].[&curds](";
end; end;
else do; else do;
/* just a placeholder - we filter out views at the top */
put "create view [&schema].[&curds]("; put "create view [&schema].[&curds](";
end; end;
put " "@@; put " "@@;
@@ -302,6 +311,17 @@ run;
put "CREATE SCHEMA &schema;"; put "CREATE SCHEMA &schema;";
%do x=1 %to %sysfunc(countw(&dsnlist)); %do x=1 %to %sysfunc(countw(&dsnlist));
%let curds=%scan(&dsnlist,&x); %let curds=%scan(&dsnlist,&x);
%local curdsvarcount;
%let curdsvarcount=%mf_getvarcount(&libref..&curds);
%if &curdsvarcount>1600 %then %do;
data _null_;
file &fref mod;
put "/* &libref..&curds contains &curdsvarcount vars */";
put "/* Postgres cannot create tables with over 1600 vars */";
put "/* No DDL will be generated for this table";
run;
%end;
%else %do;
data _null_; data _null_;
file &fref mod; file &fref mod;
put "/* Postgres Flavour DDL for &schema..&curds */"; put "/* Postgres Flavour DDL for &schema..&curds */";
@@ -314,6 +334,7 @@ run;
put "CREATE TABLE &schema..&curds ("; put "CREATE TABLE &schema..&curds (";
end; end;
else do; else do;
/* just a placeholder - we filter out views at the top */
put "CREATE VIEW &schema..&curds ("; put "CREATE VIEW &schema..&curds (";
end; end;
put " "@@; put " "@@;
@@ -355,18 +376,16 @@ run;
); );
file &fref mod; file &fref mod;
by idxusage indxname; by idxusage indxname;
/* ds=cats(libname,'.',memname); */
if first.indxname then do; if first.indxname then do;
put 'CREATE UNIQUE INDEX "' indxname +(-1) '" ' "ON &schema..&curds("; put 'CREATE UNIQUE INDEX "' indxname +(-1) '" ' "ON &schema..&curds(";
put ' "' name +(-1) '"' ; put ' "' name +(-1) '"' ;
end; end;
else put ' ,"' name +(-1) '"'; else put ' ,"' name +(-1) '"';
*else put ' ,' name ;
if last.indxname then do; if last.indxname then do;
put ');'; put ');';
end; end;
run; run;
%end;
%end; %end;
%end; %end;
%if %upcase(&showlog)=YES %then %do; %if %upcase(&showlog)=YES %then %do;
@@ -378,4 +397,4 @@ run;
run; run;
%end; %end;
%mend; %mend mp_getddl;

View File

@@ -70,4 +70,4 @@ create table &outds (rename=(
out=&outds(rename=(_name_=NAME COL1=MAXLEN)); out=&outds(rename=(_name_=NAME COL1=MAXLEN));
run; run;
%mend; %mend mp_getmaxvarlengths;

53
base/mp_gsubfile.sas Normal file
View File

@@ -0,0 +1,53 @@
/**
@file
@brief Performs a text substitution on a file
@details Makes use of the GSUB function in LUA to perform a text substitution
in a file - either in-place, or writing to a new location. The benefit of
using LUA is that the entire file can be loaded into a single variable,
thereby side stepping the 32767 character limit in a data step.
Usage:
%let file=%sysfunc(pathname(work))/file.txt;
%let str=replace/me;
%let rep=with/this;
data _null_;
file "&file";
put "&str";
run;
%mp_gsubfile(file=&file, patternvar=str, replacevar=rep)
data _null_;
infile "&file";
input;
list;
run;
@param file= (0) The file to perform the substitution on
@param patternvar= A macro variable containing the Lua
[pattern](https://www.lua.org/pil/20.2.html) to search for. Due to the use
of special (magic) characters in Lua patterns, it is safer to pass the NAME
of the macro variable containing the string, rather than the value itself.
@param replacevar= The name of the macro variable containing the replacement
_string_.
@param outfile= (0) The file to write the output to. If zero, then the file
is overwritten in-place.
<h4> SAS Macros </h4>
@li ml_gsubfile.sas
<h4> Related Macros </h4>
@li mp_gsubfile.test.sas
@version 9.4
@author Allan Bowe
**/
%macro mp_gsubfile(file=0,
patternvar=,
replacevar=,
outfile=0
)/*/STORE SOURCE*/;
%ml_gsubfile()
%mend mp_gsubfile;

View File

@@ -301,4 +301,4 @@
%return; %return;
%end; %end;
%mend; %mend mp_guesspk;

View File

@@ -20,6 +20,7 @@
@li mf_getvartype.sas @li mf_getvartype.sas
@param [in] libds dataset to hash @param [in] libds dataset to hash
@param [in] salt= Provide a salt (could be, for instance, the dataset name)
@param [out] outds= (work.mf_hashdataset) The output dataset to create. This @param [out] outds= (work.mf_hashdataset) The output dataset to create. This
will contain one column (hashkey) with one observation (a hex32. will contain one column (hashkey) with one observation (a hex32.
representation of the input hash) representation of the input hash)
@@ -33,7 +34,8 @@
%macro mp_hashdataset( %macro mp_hashdataset(
libds, libds,
outds= outds=,
salt=
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%if %mf_getattrn(&libds,NLOBS)=0 %then %do; %if %mf_getattrn(&libds,NLOBS)=0 %then %do;
%put %str(WARN)ING: Dataset &libds is empty;, or is not a dataset; %put %str(WARN)ING: Dataset &libds is empty;, or is not a dataset;
@@ -53,7 +55,7 @@
%let varlist=%mf_getvarlist(&libds); %let varlist=%mf_getvarlist(&libds);
data &outds(rename=(&keyvar=hashkey) keep=&keyvar); data &outds(rename=(&keyvar=hashkey) keep=&keyvar);
length &prevkeyvar &keyvar $32; length &prevkeyvar &keyvar $32;
retain &prevkeyvar; retain &prevkeyvar "%sysfunc(md5(%str(&salt)),$hex32.)";
set &libds end=&lastvar; set &libds end=&lastvar;
/* hash should include previous row */ /* hash should include previous row */
&keyvar=put(md5(&prevkeyvar &keyvar=put(md5(&prevkeyvar
@@ -72,4 +74,4 @@
if &lastvar then output; if &lastvar then output;
run; run;
%end; %end;
%mend; %mend mp_hashdataset;

104
base/mp_include.sas Normal file
View File

@@ -0,0 +1,104 @@
/**
@file
@brief Performs a wrapped \%include
@details This macro wrapper is necessary if you need your included code to
know that it is being \%included.
If you are using %include in a regular program, you could make use of the
following macro variables:
@li SYSINCLUDEFILEDEVICE
@li SYSINCLUDEFILEDIR
@li SYSINCLUDEFILEFILEREF
@li SYSINCLUDEFILENAME
However these variables are NOT available inside a macro, as documented here:
https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/mcrolref/n1j5tcc0n2xczyn1kg1o0606gsv9.htm
This macro can be used in place of the %include statement, and will insert
the following (equivalent) global variables:
@li _SYSINCLUDEFILEDEVICE
@li _SYSINCLUDEFILEDIR
@li _SYSINCLUDEFILEFILEREF
@li _SYSINCLUDEFILENAME
These can be used whenever testing _within a macro_. Outside of the macro,
the regular automatic variables will still be available (thanks to a
concatenated file list in the include statement).
Example usage:
filename example temp;
data _null_;
file example;
put '%macro test();';
put '%put &=_SYSINCLUDEFILEFILEREF;';
put '%put &=SYSINCLUDEFILEFILEREF;';
put '%mend; %test()';
put '%put &=SYSINCLUDEFILEFILEREF;';
run;
%mp_include(example)
@param [in] fileref The fileref of the file to be included. Must be provided.
@param [in] prefix= (_) The prefix to apply to the global variables.
@param [in] opts= (SOURCE2) The options to apply to the %inc statement
@param [in] errds= (work.mp_abort_errds) There is no clean way to end a
process within a %include called within a macro. Furthermore, there is no
way to test if a macro is called within a %include. To handle this
particular scenario, the %mp_abort() macro will test for the existence of
the `_SYSINCLUDEFILEDEVICE` variable and return the outputs (msg,mac) inside
this dataset.
It will then run an abort cancel FILE to stop the include running, and pass
the dataset back.
NOTE - it is NOT possible to read this dataset as part of _this_ macro -
when running abort cancel FILE, ALL macros are closed, so instead it is
necessary to invoke "%mp_abort(mode=INCLUDE)" OUTSIDE of any macro wrappers.
@version 9.4
@author Allan Bowe
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mp_abort.sas
**/
%macro mp_include(fileref
,prefix=_
,opts=SOURCE2
,errds=work.mp_abort_errds
)/*/STORE SOURCE*/;
/* prepare precode */
%local tempref;
%let tempref=%mf_getuniquefileref();
data _null_;
file &tempref;
set sashelp.vextfl(where=(fileref="%upcase(&fileref)"));
put '%let _SYSINCLUDEFILEDEVICE=' xengine ';';
name=scan(xpath,-1,'/\');
put '%let _SYSINCLUDEFILENAME=' name ';';
path=subpad(xpath,1,length(xpath)-length(name)-1);
put '%let _SYSINCLUDEFILEDIR=' path ';';
put '%let _SYSINCLUDEFILEFILEREF=' "&fileref;";
run;
/* prepare the errds */
data &errds;
length msg mac $1000;
iftrue='1=0';
run;
/* include the include */
%inc &tempref &fileref/&opts;
%mp_abort(iftrue= (&syscc ne 0)
,mac=%str(&_SYSINCLUDEFILEDIR/&_SYSINCLUDEFILENAME)
,msg=%str(syscc=&syscc after executing &_SYSINCLUDEFILENAME)
)
filename &tempref clear;
%mend mp_include;

View File

@@ -4,7 +4,7 @@
@details PROC JSON is faster but will produce errs like the ones below if @details PROC JSON is faster but will produce errs like the ones below if
special chars are encountered. special chars are encountered.
> ERROR: Some code points did not transcode. > (ERR)OR: Some code points did not transcode.
> An object or array close is not valid at this point in the JSON text. > An object or array close is not valid at this point in the JSON text.

View File

@@ -75,4 +75,4 @@ select distinct lowcase(memname)
) )
%end; %end;
%mend; %mend mp_lib2cards;

77
base/mp_lib2inserts.sas Normal file
View File

@@ -0,0 +1,77 @@
/**
@file
@brief Convert all data in a library to SQL insert statements
@details Gets list of members then calls the <code>%mp_ds2inserts()</code>
macro.
Usage:
%mp_getddl(sashelp, schema=work, fref=tempref)
%mp_lib2inserts(sashelp, schema=work, outref=tempref)
%inc tempref;
The output will be one file in the outref fileref.
<h4> SAS Macros </h4>
@li mp_ds2inserts.sas
@param [in] lib Library in which to convert all datasets to inserts
@param [in] flavour= (SAS) The SQL flavour to be applied to the output. Valid
options:
@li SAS (default) - suitable for regular proc sql
@li PGSQL - Used for Postgres databases
@param [in] maxobs= (max) The max number of observations (per table) to create
@param [out] outref= Output fileref in which to create the insert statements.
If it exists, it will be appended to, otherwise it will be created.
@param [out] schema= (0) The schema of the target database, or the libref.
@param [in] applydttm= (YES) If YES, any columns using datetime formats will
be converted to native DB datetime literals
@version 9.2
@author Allan Bowe
**/
%macro mp_lib2inserts(lib
,flavour=SAS
,outref=0
,schema=0
,maxobs=max
,applydttm=YES
)/*/STORE SOURCE*/;
/* Find the tables */
%local x ds memlist;
proc sql noprint;
select distinct lowcase(memname)
into: memlist
separated by ' '
from dictionary.tables
where upcase(libname)="%upcase(&lib)"
and memtype='DATA'; /* exclude views */
%let flavour=%upcase(&flavour);
%if &flavour ne SAS and &flavour ne PGSQL %then %do;
%put %str(WAR)NING: &flavour is not supported;
%return;
%end;
/* create the inserts */
%do x=1 %to %sysfunc(countw(&memlist));
%let ds=%scan(&memlist,&x);
%mp_ds2inserts(&lib..&ds
,outref=&outref
,schema=&schema
,outds=&ds
,flavour=&flavour
,maxobs=&maxobs
,applydttm=&applydttm
)
%end;
%mend mp_lib2inserts;

View File

@@ -39,4 +39,4 @@
,dttm=%sysfunc(datetime()); ,dttm=%sysfunc(datetime());
quit; quit;
%mend; %mend mp_perflog;

View File

@@ -85,4 +85,4 @@
"with record &record and " _n_=; "with record &record and " _n_=;
%end; %end;
%mend; %mend mp_prevobs;

View File

@@ -87,4 +87,4 @@ insert into &outds select distinct * from &append_ds;
) )
%end; %end;
%mend; %mend mp_recursivejoin;

View File

@@ -30,4 +30,4 @@ data _null_;
end; end;
run; run;
%mend; %mend mp_resetoption;

View File

@@ -46,4 +46,4 @@
%mend; %mend mp_runddl;

View File

@@ -117,4 +117,4 @@ proc sql
%put process finished at %sysfunc(datetime(),datetime19.); %put process finished at %sysfunc(datetime(),datetime19.);
%mend; %mend mp_searchdata;

View File

@@ -49,4 +49,4 @@
quit; quit;
%mend; %mend mp_setkeyvalue;

View File

@@ -71,4 +71,4 @@
proc append base=&libds data=&syslast nowarn;run; proc append base=&libds data=&syslast nowarn;run;
options &etls_syntaxcheck; options &etls_syntaxcheck;
%mend; %mend mp_stprequests;

View File

@@ -134,4 +134,4 @@ run;
%mp_binarycopy(inloc="&inloc",outref=_webout) %mp_binarycopy(inloc="&inloc",outref=_webout)
%end; %end;
%mend; %mend mp_streamfile;

View File

@@ -89,4 +89,4 @@ quit;
libname &lib clear; libname &lib clear;
%mend; %mend mp_testjob;

View File

@@ -22,8 +22,11 @@
|mustbevalidname|can be anything, oops, %abort!!| |mustbevalidname|can be anything, oops, %abort!!|
@param [in] debug= (log) Provide the _debug value @param [in] debug= (log) Provide the _debug value
@param [in] viyaresult=(WEBOUT_JSON) The Viya result type to return. For @param [in] mdebug= (0) Set to 1 to provide macro debugging
@param [in] viyaresult= (WEBOUT_JSON) The Viya result type to return. For
more info, see mv_getjobresult.sas more info, see mv_getjobresult.sas
@param [in] viyacontext= (SAS Job Execution compute context) The Viya compute
context on which to run the service
@param [out] outlib= (0) Output libref to contain the final tables. Set to @param [out] outlib= (0) Output libref to contain the final tables. Set to
0 if the service output is not in JSON format. 0 if the service output is not in JSON format.
@param [out] outref= (0) Output fileref to create, to contain the full _webout @param [out] outref= (0) Output fileref to create, to contain the full _webout
@@ -47,17 +50,18 @@
inputfiles=0, inputfiles=0,
inputparams=0, inputparams=0,
debug=log, debug=log,
mdebug=0,
outlib=0, outlib=0,
outref=0, outref=0,
viyaresult=WEBOUT_JSON viyaresult=WEBOUT_JSON,
viyacontext=SAS Job Execution compute context
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local mdebug; %local dbg;
%if &debug ne 0 %then %do; %if &mdebug=1 %then %do;
%let mdebug=1;
%put &sysmacroname entry vars:; %put &sysmacroname entry vars:;
%put _local_; %put _local_;
%end; %end;
%else %let mdebug=0; %else %let dbg=*;
/* sanitise inputparams */ /* sanitise inputparams */
%local pcnt; %local pcnt;
@@ -212,6 +216,7 @@
data &ds1; data &ds1;
retain _program "&program"; retain _program "&program";
retain _contextname "&viyacontext";
set &ds1; set &ds1;
putlog "&sysmacroname inputparams:"; putlog "&sysmacroname inputparams:";
putlog (_all_)(=); putlog (_all_)(=);

View File

@@ -56,4 +56,4 @@ data &outds;
duration_seconds=end_dttm-start_dttm; duration_seconds=end_dttm-start_dttm;
run; run;
%mend; %mend mp_testwritespeedlibrary;

View File

@@ -68,4 +68,4 @@ data &outds ;
rc=filename('tmp'); rc=filename('tmp');
run; run;
%mend; %mend mp_tree;

View File

@@ -63,4 +63,4 @@ data _null_;
!!'filename &fname2 clear; filename &fname3 clear;'); !!'filename &fname2 clear; filename &fname3 clear;');
run; run;
%mend; %mend mp_unzip;

View File

@@ -90,4 +90,4 @@ alter table &libds modify &var char(&len);
%mp_createconstraints(inds=&dsconst,outds=&dsconst._addd,execute=YES) %mp_createconstraints(inds=&dsconst,outds=&dsconst._addd,execute=YES)
%mend; %mend mp_updatevarlength;

59
base/mp_webin.sas Normal file
View File

@@ -0,0 +1,59 @@
/**
@file
@brief Fix the `_WEBIN` variables provided to SAS web services
@details When uploading files to SAS Stored Processes or Viya Jobs a number
of global macro variables are automatically created - however there are some
differences in behaviour both between SAS 9 and Viya, and also between a
single file upload and a multi-file upload.
This macro "straightens" up the global macro variables to make it easier /
simpler to write code that works in both environments and with a variable
number of file inputs.
After running this macro, the following global variables will *always* exist:
@li `_WEBIN_FILE_COUNT`
@li `_WEBIN_FILENAME1`
@li `_WEBIN_FILEREF1`
@li `_WEBIN_NAME1`
Usage:
%mp_webin()
This was created as a macro procedure (over a macro function) as it will also
use the filename statement in Viya environments (where `_webin_fileuri` is
provided).
<h4> SAS Macros </h4>
@li mf_getplatform.sas
@li mf_getuniquefileref.sas
**/
%macro mp_webin();
/* prepare global variables */
%global _webin_file_count
_webin_filename _webin_filename1
_webin_fileref _webin_fileref1
_webin_fileuri _webin_fileuri1
_webin_name _webin_name1
;
/* create initial versions */
%let _webin_file_count=%eval(&_webin_file_count+0);
%let _webin_filename1=%sysfunc(coalescec(&_webin_filename1,&_webin_filename));
%let _webin_fileref1=%sysfunc(coalescec(&_webin_fileref1,&_webin_fileref));
%let _webin_fileuri1=%sysfunc(coalescec(&_webin_fileuri1,&_webin_fileuri));
%let _webin_name1=%sysfunc(coalescec(&_webin_name1,&_webin_name));
/* If Viya, create temporary fileref(s) */
%local i;
%if %mf_getplatform()=SASVIYA %then %do i=1 %to &_webin_file_count;
%let _webin_fileref&i=%mf_getuniquefileref();
filename &&_webin_fileref&i filesrvc "&&_webin_fileuri&i";
%end;
%mend mp_webin;

View File

@@ -76,4 +76,4 @@ ods package publish archive properties
(archive_name="&outname..zip" archive_path="&outpath"); (archive_name="&outname..zip" archive_path="&outpath");
ods package close; ods package close;
%mend; %mend mp_zip;

View File

@@ -23,7 +23,7 @@ for file in files:
ml.write(" put '" + line.rstrip().replace("'","''") + " ';\n") ml.write(" put '" + line.rstrip().replace("'","''") + " ';\n")
ml.write("run;\n\n") ml.write("run;\n\n")
ml.write("%inc \"%sysfunc(pathname(work))/" + name + ".lua\";\n\n") ml.write("%inc \"%sysfunc(pathname(work))/" + name + ".lua\";\n\n")
ml.write("%mend;\n") ml.write("%mend " + name + ";\n")
ml.close() ml.close()
@@ -84,7 +84,7 @@ options noquotelenmax;
""" """
f = open('all.sas', "w") # r / r+ / rb / rb+ / w / wb f = open('all.sas', "w") # r / r+ / rb / rb+ / w / wb
f.write(header) f.write(header)
folders=['base','meta','metax','viya','lua'] folders=['base','meta','metax','viya','lua','fcmp']
for folder in folders: 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() filenames.sort()

View File

@@ -0,0 +1,94 @@
/**
@file
@brief Provides a replacement for the stpsrv_header function
@details The stpsrv_header is normally a built-in function, used to set the
headers for SAS 9 Stored Processes as documented here:
https://go.documentation.sas.com/doc/en/itechcdc/9.4/stpug/srvhead.htm
The purpose of this custom function is to provide a replacement when running
similar code as a web service against
[sasjs/server](https://github.com/sasjs/server). It operates by creating a
text file with the headers. The location of this text file is determined by
a macro variable (`sasjs_stpsrv_header_loc`) which needs to be injected into
each service by the calling process, eg:
%let sasjs_stpsrv_header_loc = C:/temp/some_uuid/stpsrv_header.txt;
Note - the function works by appending headers to the file. If multiple same-
named headers are provided, they will all be appended - the calling process
needs to pick up the last one. This will mean removing the attribute if the
final record has an empty value.
The function takes the following (positional) parameters:
| PARAMETER | DESCRIPTION |
|------------|-------------|
| name $ | name of the header attribute to create|
| value $ | value of the header attribute|
It returns 0 if successful, or -1 if an error occured.
Usage:
%let sasjs_stpsrv_header_loc=%sysfunc(pathname(work))/stpsrv_header.txt;
%mcf_stpsrv_header(wrap=YES, insert_cmplib=YES)
data _null_;
rc=stpsrv_header('Content-type','application/text');
rc=stpsrv_header('Content-disposition',"attachment; filename=file.txt");
run;
data _null_;
infile "&sasjs_stpsrv_header_loc";
input;
putlog _infile_;
run;
@param [out] wrap= (NO) Choose YES to add the proc fcmp wrapper.
@param [out] insert_cmplib= (NO) Choose YES to insert the package into the
CMPLIB reference.
@param [out] lib= (work) The output library in which to create the catalog.
@param [out] cat= (sasjs) The output catalog in which to create the package.
@param [out] pkg= (utils) The output package in which to create the function.
Uses a 3 part format: libref.catalog.package
**/
%macro mcf_stpsrv_header(wrap=NO
,insert_cmplib=NO
,lib=WORK
,cat=SASJS
,pkg=UTILS
)/*/STORE SOURCE*/;
%if &wrap=YES %then %do;
proc fcmp outcat=&lib..&cat..&pkg;
%end;
function stpsrv_header(name $, value $);
length loc $128 val $512;
loc=symget('sasjs_stpsrv_header_loc');
val=trim(name)!!': '!!value;
length fref $8;
rc=filename(fref,loc);
if (rc ne 0) then return( -1 );
fid = fopen(fref,'a');
if (fid = 0) then return( -1 );
rc=fput(fid, val);
rc=fwrite(fid);
rc=fclose(fid);
rc=filename(fref);
return(0);
endsub;
%if &wrap=YES %then %do;
quit;
%end;
%if &insert_cmplib=YES %then %do;
options insert=(CMPLIB=(&lib..&cat));
%end;
%mend mcf_stpsrv_header;

79
fcmp/mcf_string2file.sas Normal file
View File

@@ -0,0 +1,79 @@
/**
@file
@brief Adds a string to a file
@details Creates an fcmp function for appending a string to an external file.
If the file does not exist, it is created.
The function itself takes the following (positional) parameters:
| PARAMETER | DESCRIPTION |
|------------|-------------|
| filepath $ | full path to the file|
| string $ | string to add to the file |
| mode $ | mode of the output - either APPEND (default) or CREATE |
It returns 0 if successful, or -1 if an error occured.
Usage:
%mcf_string2file(wrap=YES, insert_cmplib=YES)
data _null_;
rc=mcf_string2file(
"%sysfunc(pathname(work))/newfile.txt"
, "This is a test"
, "CREATE");
run;
data _null_;
infile "%sysfunc(pathname(work))/newfile.txt";
input;
putlog _infile_;
run;
@param [out] wrap= (NO) Choose YES to add the proc fcmp wrapper.
@param [out] insert_cmplib= (NO) Choose YES to insert the package into the
CMPLIB reference.
@param [out] lib= (work) The output library in which to create the catalog.
@param [out] cat= (sasjs) The output catalog in which to create the package.
@param [out] pkg= (utils) The output package in which to create the function.
Uses a 3 part format: libref.catalog.package
**/
%macro mcf_string2file(wrap=NO
,insert_cmplib=NO
,lib=WORK
,cat=SASJS
,pkg=UTILS
)/*/STORE SOURCE*/;
%if &wrap=YES %then %do;
proc fcmp outcat=&lib..&cat..&pkg;
%end;
function mcf_string2file(filepath $, string $, mode $);
if mode='APPEND' then fmode='a';
else fmode='o';
length fref $8;
rc=filename(fref,filepath);
if (rc ne 0) then return( -1 );
fid = fopen(fref,fmode);
if (fid = 0) then return( -1 );
rc=fput(fid, string);
rc=fwrite(fid);
rc=fclose(fid);
rc=filename(fref);
return(0);
endsub;
%if &wrap=YES %then %do;
quit;
%end;
%if &insert_cmplib=YES %then %do;
options insert=(CMPLIB=(&lib..&cat));
%end;
%mend mcf_string2file;

25
lua/gsubfile.lua Normal file
View File

@@ -0,0 +1,25 @@
local fpath, outpath, file, fcontent
-- configure in / out paths
fpath = sas.symget("file")
outpath = sas.symget("outfile")
if ( outpath == 0 )
then
outpath=fpath
end
-- open file and perform the substitution
file = io.open(fpath,"r")
fcontent = file:read()
file:close()
fcontent = string.gsub(
fcontent,
sas.symget(sas.symget("patternvar")),
sas.symget(sas.symget("replacevar"))
)
-- write the file back out
file = io.open(outpath, "w+")
io.output(file)
io.write(fcontent)
io.close(file)

44
lua/ml_gsubfile.sas Normal file
View File

@@ -0,0 +1,44 @@
/**
@file ml_gsubfile.sas
@brief Compiles the gsubfile.lua lua file
@details Writes gsubfile.lua to the work directory
and then includes it.
Usage:
%ml_gsubfile()
**/
%macro ml_gsubfile();
data _null_;
file "%sysfunc(pathname(work))/ml_gsubfile.lua";
put 'local fpath, outpath, file, fcontent ';
put ' ';
put '-- configure in / out paths ';
put 'fpath = sas.symget("file") ';
put 'outpath = sas.symget("outfile") ';
put 'if ( outpath == 0 ) ';
put 'then ';
put ' outpath=fpath ';
put 'end ';
put ' ';
put '-- open file and perform the substitution ';
put 'file = io.open(fpath,"r") ';
put 'fcontent = file:read() ';
put 'file:close() ';
put 'fcontent = string.gsub( ';
put ' fcontent, ';
put ' sas.symget(sas.symget("patternvar")), ';
put ' sas.symget(sas.symget("replacevar")) ';
put ') ';
put ' ';
put '-- write the file back out ';
put 'file = io.open(outpath, "w+") ';
put 'io.output(file) ';
put 'io.write(fcontent) ';
put 'io.close(file) ';
run;
%inc "%sysfunc(pathname(work))/ml_gsubfile.lua";
%mend ml_gsubfile;

View File

@@ -391,4 +391,4 @@ run;
%inc "%sysfunc(pathname(work))/ml_json.lua"; %inc "%sysfunc(pathname(work))/ml_json.lua";
%mend; %mend ml_json;

View File

@@ -20,6 +20,19 @@
*/ */
/*! \dir fcmp
* \brief Macros for generating FCMP functions
* \details These macros have the following attributes:
* Macro name matches compiled function / subroutine name
* Prefixes: _mcf_, _mcs_
The macro part is just a wrapper for the underlying function / subroutine,
and has switches for including the proc fcmp / quit statements and whether
to insert the package into the CMPLIB option.
*/
/*! \dir meta /*! \dir meta
* \brief Metadata Aware Macros * \brief Metadata Aware Macros
* \details These macros have the following attributes: * \details These macros have the following attributes:

View File

@@ -95,4 +95,4 @@ run;
filename __us2grp clear; filename __us2grp clear;
%mend; %mend mm_adduser2group;

View File

@@ -460,4 +460,4 @@ run;
%return; %return;
%end; %end;
%mend; %mend mm_assigndirectlib;

View File

@@ -75,4 +75,4 @@
%else %do; %else %do;
%put NOTE: Library &libref is already assigned; %put NOTE: Library &libref is already assigned;
%end; %end;
%mend; %mend mm_assignlib;

View File

@@ -152,4 +152,4 @@ run;
%else %put NOTE: Application (&name) successfully created in (&tree)!; %else %put NOTE: Application (&name) successfully created in (&tree)!;
%mend; %mend mm_createapplication;

View File

@@ -80,4 +80,4 @@ data _null_;
if last then call execute('call missing(of _all_);stop;run;'); if last then call execute('call missing(of _all_);stop;run;');
run; run;
%mend; %mend mm_createdataset;

View File

@@ -122,4 +122,4 @@ run;
run; run;
%end; %end;
%mend; %mend mm_createdocument;

View File

@@ -16,8 +16,8 @@
%mm_createfolder(path=/some/meta/folder) %mm_createfolder(path=/some/meta/folder)
@param path= Name of the folder to create. @param [in] path= Name of the folder to create.
@param mdebug= set DBG to 1 to disable DEBUG messages @param [in] mdebug= set DBG to 1 to disable DEBUG messages
@version 9.4 @version 9.4
@author Allan Bowe @author Allan Bowe
@@ -157,4 +157,4 @@ run;
%end; %end;
%put &sysmacroname: execution finished for &path; %put &sysmacroname: execution finished for &path;
%mend; %mend mm_createfolder;

View File

@@ -318,4 +318,4 @@ filename &frefout temp;
filename &frefout clear; filename &frefout clear;
%end; %end;
%mend; %mend mm_createlibrary;

View File

@@ -385,4 +385,4 @@ run;
%put %str(WARN)ING: STPTYPE=*&stptype* not recognised!; %put %str(WARN)ING: STPTYPE=*&stptype* not recognised!;
%end; %end;
%mend; %mend mm_createstp;

View File

@@ -269,7 +269,7 @@ data _null_;
put '%local i tempds jsonengine; '; put '%local i tempds jsonengine; ';
put ' '; put ' ';
put '/* see https://github.com/sasjs/core/issues/41 */ '; put '/* see https://github.com/sasjs/core/issues/41 */ ';
put '%if %upcase(&SYSENCODING)=WLATIN1 %then %let jsonengine=PROCJSON; '; put '%if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON; ';
put '%else %let jsonengine=DATASTEP; '; put '%else %let jsonengine=DATASTEP; ';
put ' '; put ' ';
put ' '; put ' ';
@@ -415,7 +415,7 @@ data _null_;
put ' '; put ' ';
put ' %quote(&user) '; put ' %quote(&user) ';
put ' '; put ' ';
put '%mend; '; put '%mend mf_getuser; ';
/* WEBOUT END */ /* WEBOUT END */
put '%macro webout(action,ds,dslabel=,fmt=);'; put '%macro webout(action,ds,dslabel=,fmt=);';
put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt)'; put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt)';

View File

@@ -68,4 +68,4 @@ run;
%return; %return;
%end; %end;
%mend; %mend mm_deletedocument;

Some files were not shown because too many files have changed in this diff Show More