mirror of
https://github.com/sasjs/core.git
synced 2025-12-11 06:24:35 +00:00
Compare commits
289 Commits
v4.30.1
...
all-contri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1db610a122 | ||
|
|
c9e431142c | ||
|
|
2b2aa5eb58 | ||
|
|
1ac2b480a6 | ||
|
|
4e53544b66 | ||
|
|
9b5f1cf170 | ||
|
|
703fe4ef38 | ||
|
|
f4a4263046 | ||
|
|
02bf9c85db | ||
|
|
5835cfaa83 | ||
|
|
b50521a8de | ||
|
|
fccd6fcc44 | ||
|
|
487ff5faa9 | ||
|
|
5efc20eacc | ||
|
|
cbd62fbfab | ||
|
|
2808145302 | ||
|
|
815e5f3e0e | ||
|
|
843d6e5c2d | ||
|
|
b084f4e84b | ||
|
|
5b5116070e | ||
|
|
a2002db838 | ||
|
|
dc6bcdd69e | ||
|
|
c97dc9a16d | ||
|
|
ef669db622 | ||
|
|
26499d2058 | ||
|
|
17e5d0f0e0 | ||
|
|
fc9205e355 | ||
|
|
ce344fc8e2 | ||
|
|
40239c53d8 | ||
|
|
814ecec94f | ||
|
|
934c501fec | ||
|
|
091b2e28be | ||
|
|
d8ea29bf8c | ||
|
|
993dec4610 | ||
|
|
f905387d66 | ||
|
|
7512423b04 | ||
|
|
50e6d416a4 | ||
|
|
18b6cadce6 | ||
|
|
413743bbe6 | ||
|
|
fcafb1026e | ||
|
|
b8f24264d4 | ||
|
|
5eb87a754e | ||
|
|
3a5fd4bfc5 | ||
|
|
b7ae9a2737 | ||
|
|
4057ac4b2e | ||
|
|
fa0a6ab22d | ||
|
|
2ae7a60be5 | ||
|
|
0a24f3ff7b | ||
|
|
592f477063 | ||
|
|
a91db81894 | ||
|
|
236e7cc4c0 | ||
|
|
2b6882cb9c | ||
|
|
2a3071708a | ||
|
|
3890aefccf | ||
|
|
f378a5637f | ||
|
|
121c692e09 | ||
|
|
759d6bd144 | ||
|
|
76d248f302 | ||
|
|
f30e30c024 | ||
|
|
30637b5025 | ||
|
|
9f16d090f5 | ||
|
|
74143bdf29 | ||
|
|
1c4c9793f6 | ||
|
|
d42fd4ebac | ||
|
|
d39b1be7a8 | ||
|
|
65d4c7969d | ||
|
|
fa152cb375 | ||
|
|
91a2d9039b | ||
|
|
73f919ffe7 | ||
|
|
68c11334df | ||
|
|
3bb83be0c5 | ||
|
|
76a20838ec | ||
|
|
9eec2e4920 | ||
|
|
bd18d4c32d | ||
|
|
d7763e276f | ||
|
|
7dadcf20f4 | ||
|
|
a497976eae | ||
|
|
6c64de651d | ||
|
|
48c17beb20 | ||
|
|
c46bb92c39 | ||
|
|
1e894bae98 | ||
|
|
461cda45ee | ||
|
|
7b6d34028b | ||
|
|
cb05ee2b9a | ||
|
|
e41b91f495 | ||
|
|
d21958cf0b | ||
|
|
c4b445db77 | ||
|
|
ebe764a7c0 | ||
|
|
7bba51a60e | ||
|
|
bce810caa0 | ||
|
|
222161d589 | ||
|
|
70cac82d78 | ||
|
|
6e0b8ae13b | ||
|
|
0dc4bbab62 | ||
|
|
da5244cda9 | ||
|
|
724de80d0f | ||
|
|
8de2dd4e7c | ||
|
|
e46165c140 | ||
|
|
a9185a2bf2 | ||
|
|
f0b77dfc6a | ||
|
|
91c4b87496 | ||
|
|
111d0dffc3 | ||
|
|
4f481ec8b4 | ||
|
|
b8cec22a88 | ||
|
|
6b1accdd6b | ||
|
|
949b406c23 | ||
|
|
fc90a7f928 | ||
|
|
d0bd88907f | ||
| 4e21772207 | |||
|
|
39f700bed2 | ||
|
|
dcb7958950 | ||
|
|
146610b5a7 | ||
|
|
9887efcf60 | ||
|
|
a2c7bdafb4 | ||
|
|
c58b5c7a52 | ||
|
|
22c7e5b4dd | ||
|
|
244171f8c4 | ||
|
|
fd765e2d68 | ||
| 840cb5ef44 | |||
| 918ce96fce | |||
| f1712c34e8 | |||
| 11ec20b472 | |||
| f42f111462 | |||
| 907725c5ba | |||
| 95b78b91e1 | |||
| e4771b9c14 | |||
| ba8190883e | |||
|
|
32dd057e83 | ||
|
|
7471bd42a4 | ||
|
|
702a4ecd3a | ||
|
|
5da97295ff | ||
|
|
dc556bdef0 | ||
|
|
111731bf35 | ||
|
|
2c526cf9dd | ||
|
|
660e02193f | ||
|
|
00b4dee86e | ||
|
|
3913825c22 | ||
|
|
0f143d603b | ||
|
|
f1d5fa2c0a | ||
|
|
a88689428f | ||
|
|
8843fa8bfc | ||
|
|
22d046cf5c | ||
|
|
29e3eb34aa | ||
|
|
1af52a6683 | ||
|
|
fc0c96dd94 | ||
|
|
b9c4882553 | ||
|
|
011b2b185c | ||
|
|
dbc23550ac | ||
|
|
8910840ccc | ||
|
|
4ef571032d | ||
|
|
e01cd8cd16 | ||
|
|
00628ec78a | ||
|
|
f4e6a487f3 | ||
|
|
b7afecdf81 | ||
|
|
19eb348f0e | ||
|
|
f420ac2abf | ||
|
|
7edec1ad8a | ||
|
|
62d7bce249 | ||
|
|
fe6c9a793b | ||
|
|
8e13943356 | ||
|
|
04df9600e0 | ||
|
|
e2b0aabfa4 | ||
|
|
c52a623630 | ||
|
|
cf348e8016 | ||
|
|
6502fc4982 | ||
|
|
ef574f6319 | ||
|
|
5b251006cd | ||
|
|
b353acec47 | ||
|
|
8b148c3916 | ||
|
|
2efdcec54c | ||
|
|
f832e93f4b | ||
|
|
f37c2e5867 | ||
|
|
6f8ec5d5a8 | ||
|
|
6521ade608 | ||
|
|
2666bbc85e | ||
|
|
ee35f47f4f | ||
|
|
7f867e2a5c | ||
|
|
c6af6ce578 | ||
|
|
a1aac785c0 | ||
|
|
dbe8b0b1c3 | ||
|
|
2ee9a4cee4 | ||
|
|
3a7afdffb7 | ||
|
|
c78211aa1c | ||
|
|
76c49e96f2 | ||
|
|
984ea44f5d | ||
|
|
88f1222abd | ||
|
|
d88f028ee3 | ||
|
|
07d7c9df4b | ||
|
|
6765a1d025 | ||
|
|
952f28a872 | ||
|
|
8246b5a42c | ||
|
|
72123aeeb7 | ||
|
|
236d1ae25f | ||
|
|
b75369b28d | ||
|
|
63871db170 | ||
|
|
6456c2f6e2 | ||
|
|
36faa194a8 | ||
|
|
093dc87aad | ||
|
|
ca045e3ebf | ||
|
|
be5e2f371d | ||
|
|
6d15465bac | ||
|
|
2031a5b0c0 | ||
|
|
7b3844a391 | ||
|
|
202de36042 | ||
|
|
62837b512b | ||
|
|
5d5a99fd77 | ||
|
|
1b5effd584 | ||
|
|
1613ab2c9e | ||
|
|
a2df4e35be | ||
|
|
aabbcfdf6b | ||
|
|
7b7759e1ce | ||
|
|
e5a3053600 | ||
|
|
9856d0ef58 | ||
|
|
77b37e5503 | ||
|
|
793319fe38 | ||
|
|
594a895ddd | ||
|
|
0d59266b8d | ||
|
|
4863aafaa8 | ||
|
|
6015320145 | ||
|
|
8c09c0bce0 | ||
|
|
437943b779 | ||
|
|
6a090e45b6 | ||
|
|
a7dc314204 | ||
|
|
37076eae89 | ||
|
|
9a9f8dc847 | ||
|
|
719b657267 | ||
|
|
671a615501 | ||
|
|
884b45bf12 | ||
|
|
ff6ae1b066 | ||
|
|
d581fec55e | ||
|
|
a5613a79bb | ||
|
|
c6703e16e8 | ||
|
|
6587dce95b | ||
|
|
b60e6448b9 | ||
|
|
46d9b58b32 | ||
|
|
349cbabc94 | ||
|
|
9de056a3fc | ||
|
|
ad497b322f | ||
|
|
7a6408ee44 | ||
|
|
336743f2b4 | ||
|
|
6e32eb3bd6 | ||
|
|
b377b83442 | ||
|
|
899b94bb6e | ||
|
|
d97efdff61 | ||
|
|
1097afbcb8 | ||
|
|
165b2d3568 | ||
|
|
44a80c8985 | ||
|
|
6e32d9b743 | ||
|
|
6b167e7a4c | ||
|
|
011672b1ed | ||
|
|
a7eb926810 | ||
|
|
cad7f13a0e | ||
|
|
65fcea817a | ||
|
|
22fade13e7 | ||
|
|
7146310072 | ||
|
|
b7de1c25ec | ||
|
|
f4c7f47ffe | ||
|
|
cdf339d077 | ||
|
|
31702df19b | ||
|
|
cf0d1c0473 | ||
|
|
1f369f9848 | ||
|
|
2372ff5f4f | ||
|
|
6d0e34ba1d | ||
|
|
7a69698178 | ||
|
|
532bf84e06 | ||
|
|
e1afbc02c4 | ||
|
|
756f00d88d | ||
|
|
b72e404d52 | ||
|
|
e31cdeef42 | ||
|
|
8a4e32cc27 | ||
|
|
f285505b79 | ||
|
|
67f5c50300 | ||
|
|
ce39e4f779 | ||
|
|
9c80f5664c | ||
|
|
83466c001b | ||
|
|
ad315be503 | ||
|
|
c41ae2dcc8 | ||
| d9f8e92fac | |||
|
|
d42ede15db | ||
|
|
08ea9f7c00 | ||
|
|
c327e1fc0d | ||
|
|
02fddcf9a1 | ||
|
|
4752bfbb05 | ||
|
|
767ddd7add | ||
|
|
54a24ced83 | ||
|
|
57ae2981f1 | ||
|
|
a3043ac685 | ||
|
|
2bdb90b0be | ||
|
|
2cd846d504 |
@@ -135,8 +135,36 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "henrik-forsell",
|
||||||
|
"name": "Henrik Forsell",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/109935936?v=4",
|
||||||
|
"profile": "https://github.com/henrik-forsell",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "rudvfaden",
|
||||||
|
"name": "Rud Faden",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/2445577?v=4",
|
||||||
|
"profile": "http://rudvfaden.github.io/",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "andyjessen",
|
||||||
|
"name": "andyjessen",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/62343929?v=4",
|
||||||
|
"profile": "https://github.com/andyjessen",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
"skipCi": true
|
"skipCi": true,
|
||||||
|
"commitType": "docs"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Ensure lint is passing
|
# Ensure lint is passing
|
||||||
LINT=`sasjs lint`
|
LINT=`sasjs lint`
|
||||||
if [[ "$LINT" != "✔ All matched files use @sasjs/lint code style!" ]]; then
|
if [[ "$LINT" != *"All matched files use @sasjs/lint code style!" ]]; then
|
||||||
echo "$LINT"
|
echo "$LINT"
|
||||||
echo "To commit in spite of these warnings, use the -n parameter."
|
echo "To commit in spite of these warnings, use the -n parameter."
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
3
.github/CONTRIBUTING.md
vendored
3
.github/CONTRIBUTING.md
vendored
@@ -27,5 +27,6 @@ To contribute:
|
|||||||
1. Create your feature branch (`git checkout -b myfeature`)
|
1. Create your feature branch (`git checkout -b myfeature`)
|
||||||
2. Make your change
|
2. Make your change
|
||||||
3. Update the `all.sas` file (`python3 build.py`)
|
3. Update the `all.sas` file (`python3 build.py`)
|
||||||
4. Push and make a PR
|
4. Commit using a [Conventional Commit](https://www.conventionalcommits.org)
|
||||||
|
5. Push and make a PR
|
||||||
|
|
||||||
|
|||||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,3 +1,3 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: [sasjs]
|
custom: https://getalby.com/p/sasjs
|
||||||
|
|||||||
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
## Issue
|
||||||
|
|
||||||
|
Link any related issue(s) in this section.
|
||||||
|
|
||||||
|
## Intent
|
||||||
|
|
||||||
|
What this PR intends to achieve.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
What code changes have been made to achieve the intent.
|
||||||
|
|
||||||
|
## Checks
|
||||||
|
|
||||||
|
- [ ] Code is formatted correctly (`sasjs lint`).
|
||||||
|
- [ ] Any new functionality has been unit tested.
|
||||||
|
- [ ] All unit tests are passing (`sasjs test`).
|
||||||
|
- [ ] The PR desc or underlying commits follow the [Conventional Commit](https://www.conventionalcommits.org) standard
|
||||||
41
.github/vpn/config.ovpn
vendored
41
.github/vpn/config.ovpn
vendored
@@ -1,30 +1,25 @@
|
|||||||
cipher AES-256-CBC
|
# Client
|
||||||
setenv FORWARD_COMPATIBLE 1
|
|
||||||
client
|
client
|
||||||
server-poll-timeout 4
|
tls-client
|
||||||
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 tun
|
||||||
dev-type tun
|
# this will connect with whatever proto DNS tells us (https://community.openvpn.net/openvpn/ticket/934)
|
||||||
ns-cert-type server
|
proto tcp
|
||||||
setenv opt tls-version-min 1.0 or-highest
|
remote vpn.4gl.io 7494
|
||||||
reneg-sec 604800
|
resolv-retry infinite
|
||||||
sndbuf 0
|
cipher AES-256-CBC
|
||||||
rcvbuf 0
|
auth SHA256
|
||||||
# NOTE: LZO commands are pushed by the Access Server at connect time.
|
script-security 2
|
||||||
# NOTE: The below line doesn't disable LZO.
|
keepalive 10 120
|
||||||
comp-lzo no
|
remote-cert-tls server
|
||||||
verb 3
|
|
||||||
setenv PUSH_PEER_INFO
|
|
||||||
|
|
||||||
|
# Keys
|
||||||
ca ca.crt
|
ca ca.crt
|
||||||
cert user.crt
|
cert user.crt
|
||||||
key user.key
|
key user.key
|
||||||
tls-auth tls.key 1
|
tls-auth tls.key 1
|
||||||
|
|
||||||
|
# Security
|
||||||
|
nobind
|
||||||
|
persist-key
|
||||||
|
persist-tun
|
||||||
|
verb 3
|
||||||
|
|||||||
53
.github/workflows/main.yml
vendored
53
.github/workflows/main.yml
vendored
@@ -13,9 +13,54 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
- name: Semantic Release
|
|
||||||
uses: cycjimmy/semantic-release-action@v2
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
- name: Check code style (aborts if errors found)
|
||||||
|
run: npx @sasjs/cli lint
|
||||||
|
|
||||||
|
- 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:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
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-jammy.list
|
||||||
|
#sudo apt update
|
||||||
|
#sudo apt install openvpn3=17~betaUb22042+jammy
|
||||||
|
- name: Start Open VPN 3
|
||||||
|
run: |
|
||||||
|
# openvpn3 session-start --config .github/vpn/config.ovpn
|
||||||
|
|
||||||
|
- name: Add credentials
|
||||||
|
run: |
|
||||||
|
echo "CLIENT=${{secrets.SAS9_4GL_IO_CLIENT}}"> .env.server
|
||||||
|
echo "ACCESS_TOKEN=${{secrets.SAS9_4GL_IO_ACCESS_TOKEN}}" >> .env.server
|
||||||
|
echo "REFRESH_TOKEN=${{secrets.SAS9_4GL_IO_REFRESH_TOKEN}}" >> .env.server
|
||||||
|
|
||||||
|
- name: Semantic Release
|
||||||
|
uses: cycjimmy/semantic-release-action@v4
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
|
- name: SAS Packages Release
|
||||||
|
run: |
|
||||||
|
npx @sasjs/cli compile job -s sasjs/utils/create_sas_package.sas -o sasjsbuild -t server
|
||||||
|
# need long duration token per https://github.com/sasjs/server/issues/307
|
||||||
|
# npx @sasjs/cli run sasjsbuild/jobs/utils/create_sas_package.sas -t server
|
||||||
|
|||||||
32
.github/workflows/notmain.yml
vendored
Normal file
32
.github/workflows/notmain.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
||||||
|
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
|
||||||
|
|
||||||
|
name: SASjs Core - Update all.sas
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
npm ci
|
||||||
|
npm i -g @sasjs/cli@latest
|
||||||
|
|
||||||
|
- name: Ensure all.sas is always up to date
|
||||||
|
run: |
|
||||||
|
git config user.name github-actions
|
||||||
|
git config user.email github-actions@github.com
|
||||||
|
python3 build.py
|
||||||
|
git add all.sas
|
||||||
|
git commit -m "chore: updating all.sas" --allow-empty
|
||||||
|
git push
|
||||||
|
|
||||||
|
|
||||||
31
.github/workflows/run-tests.yml
vendored
31
.github/workflows/run-tests.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [lts/fermium]
|
node-version: [lts/hydrogen]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -39,46 +39,39 @@ jobs:
|
|||||||
sudo apt install apt-transport-https
|
sudo apt install apt-transport-https
|
||||||
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
|
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
|
||||||
sudo apt-key add 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 wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-jammy.list
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install openvpn3
|
sudo apt install openvpn3=17~betaUb22042+jammy
|
||||||
|
|
||||||
- name: Start Open VPN 3
|
- name: Start Open VPN 3
|
||||||
run: openvpn3 session-start --config .github/vpn/config.ovpn
|
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
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Check code style
|
- name: Check code style (aborts if errors found)
|
||||||
run: npm run lint
|
run: npx @sasjs/cli lint
|
||||||
|
|
||||||
- name: Add client
|
- name: Add client
|
||||||
run: echo "CLIENT=${{secrets.CLIENT}}"> .env.viya
|
run: echo "CLIENT=${{secrets.SAS9_4GL_IO_CLIENT}}"> .env.server
|
||||||
|
|
||||||
- name: Add secret
|
|
||||||
run: echo "SECRET=${{secrets.SECRET}}" >> .env.viya
|
|
||||||
|
|
||||||
- name: Add access token
|
- name: Add access token
|
||||||
run: echo "ACCESS_TOKEN=${{secrets.ACCESS_TOKEN}}" >> .env.viya
|
run: echo "ACCESS_TOKEN=${{secrets.SAS9_4GL_IO_ACCESS_TOKEN}}" >> .env.server
|
||||||
|
|
||||||
- name: Add refresh token
|
- name: Add refresh token
|
||||||
run: echo "REFRESH_TOKEN=${{secrets.REFRESH_TOKEN}}" >> .env.viya
|
run: echo "REFRESH_TOKEN=${{secrets.SAS9_4GL_IO_REFRESH_TOKEN}}" >> .env.server
|
||||||
|
|
||||||
- name: Build Project
|
- name: Build & Deploy Project to SAS server
|
||||||
run: npm run build
|
run: npx @sasjs/cli cbd -t server
|
||||||
|
|
||||||
- name: Run SASjs tests
|
- name: Run all tests
|
||||||
run: npm run test
|
run: npx @sasjs/cli test -t server
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
CLIENT: ${{secrets.CLIENT}}
|
CLIENT: ${{secrets.CLIENT}}
|
||||||
SECRET: ${{secrets.SECRET}}
|
SECRET: ${{secrets.SECRET}}
|
||||||
SAS_USERNAME: ${{secrets.SAS_USERNAME}}
|
SAS_USERNAME: ${{secrets.SAS_USERNAME}}
|
||||||
SAS_PASSWORD: ${{secrets.SAS_PASSWORD}}
|
SAS_PASSWORD: ${{secrets.SAS_PASSWORD}}
|
||||||
SERVER_URL: ${{secrets.SERVER_URL}}
|
|
||||||
SERVER_TYPE: ${{secrets.SERVER_TYPE}}
|
|
||||||
ACCESS_TOKEN: ${{secrets.ACCESS_TOKEN}}
|
ACCESS_TOKEN: ${{secrets.ACCESS_TOKEN}}
|
||||||
REFRESH_TOKEN: ${{secrets.REFRESH_TOKEN}}
|
REFRESH_TOKEN: ${{secrets.REFRESH_TOKEN}}
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -10,4 +10,7 @@ sasjsresults/
|
|||||||
mc_*
|
mc_*
|
||||||
|
|
||||||
# ignore .env files as they can contain sasjs access tokens
|
# ignore .env files as they can contain sasjs access tokens
|
||||||
*.env*
|
*.env*
|
||||||
|
|
||||||
|
~
|
||||||
|
|
||||||
|
|||||||
26
.sasjslint
26
.sasjslint
@@ -1,13 +1,15 @@
|
|||||||
{
|
{
|
||||||
"noTrailingSpaces": true,
|
"noTrailingSpaces": true,
|
||||||
"noEncodedPasswords": true,
|
"noEncodedPasswords": true,
|
||||||
"hasDoxygenHeader": true,
|
"hasDoxygenHeader": true,
|
||||||
"hasMacroNameInMend": true,
|
"hasMacroNameInMend": true,
|
||||||
"hasMacroParentheses": true,
|
"hasMacroParentheses": true,
|
||||||
"noNestedMacros": false,
|
"lineEndings": "lf",
|
||||||
"noSpacesInFileNames": true,
|
"noGremlins": true,
|
||||||
"maxLineLength": 300,
|
"noNestedMacros": false,
|
||||||
"lowerCaseFileNames": true,
|
"noSpacesInFileNames": true,
|
||||||
"noTabIndentation": true,
|
"maxLineLength": 300,
|
||||||
"indentationMultiple": 2
|
"lowerCaseFileNames": true,
|
||||||
}
|
"noTabs": true,
|
||||||
|
"indentationMultiple": 2
|
||||||
|
}
|
||||||
|
|||||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -6,5 +6,7 @@
|
|||||||
"editor.rulers": [
|
"editor.rulers": [
|
||||||
80
|
80
|
||||||
],
|
],
|
||||||
"files.trimTrailingWhitespace": true
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"sasjs-for-vscode.target": "docsonly",
|
||||||
|
"sasjs-for-vscode.isLocal": true
|
||||||
}
|
}
|
||||||
67
README.md
67
README.md
@@ -2,8 +2,6 @@
|
|||||||
[![npm package][npm-image]][npm-url]
|
[![npm package][npm-image]][npm-url]
|
||||||
[![Github Workflow][githubworkflow-image]][githubworkflow-url]
|
[![Github Workflow][githubworkflow-image]][githubworkflow-url]
|
||||||

|

|
||||||

|
|
||||||
[](/LICENSE)
|
|
||||||

|

|
||||||
[](https://github.com/sasjs/core/issues?q=is%3Aissue+is%3Aclosed)
|
[](https://github.com/sasjs/core/issues?q=is%3Aissue+is%3Aclosed)
|
||||||
[](https://github.com/sasjs/core/issues)
|
[](https://github.com/sasjs/core/issues)
|
||||||
@@ -36,21 +34,21 @@ Documentation: https://core.sasjs.io
|
|||||||
- OS independent
|
- OS independent
|
||||||
- Works on all SAS Platforms
|
- Works on all SAS Platforms
|
||||||
- No X command
|
- No X command
|
||||||
- Prefixes: _mf_, _mp_
|
- Prefixes: `mf_`, `mp_`
|
||||||
|
|
||||||
### DDL folder (All Platforms)
|
### DDL folder (All Platforms)
|
||||||
|
|
||||||
- OS independent
|
- OS independent
|
||||||
- Works on all SAS Platforms
|
- Works on all SAS Platforms
|
||||||
- No X command
|
- No X command
|
||||||
- Prefixes: _mddl_(lib)_ -> where lib can be "SAS" (in relation to a SAS component) or "DC" (in relation to a Data Controller component)
|
- Prefixes: `mddl_(lib)_` -> where lib can be "SAS" (in relation to a SAS component) or "DC" (in relation to a Data Controller component)
|
||||||
|
|
||||||
This library will not be used for storing data entries (such as formats or datalines). Where this becomes necessary in the future, a new repo will be created, in order to keep the NPM bundle size down (for the benefit of those looking to embed purely macros in their applications).
|
This library will not be used for storing data entries (such as formats or datalines). Where this becomes necessary in the future, a new repo will be created, in order to keep the NPM bundle size down (for the benefit of those looking to embed purely macros in their applications).
|
||||||
|
|
||||||
### FCMP folder (All Platforms)
|
### FCMP folder (All Platforms)
|
||||||
|
|
||||||
- Function and macro names are identical, except for special cases
|
- Function and macro names are identical, except for special cases
|
||||||
- Prefixes: _mcf_
|
- Prefixes: `mcf_`
|
||||||
|
|
||||||
The fcmp macros are used to generate fcmp functions, and can be used with or without the `proc fcmp` wrapper.
|
The fcmp macros are used to generate fcmp functions, and can be used with or without the `proc fcmp` wrapper.
|
||||||
|
|
||||||
@@ -72,7 +70,7 @@ endsubmit;
|
|||||||
run;
|
run;
|
||||||
```
|
```
|
||||||
|
|
||||||
- Prefixes: _ml_
|
- Prefixes: `ml_`
|
||||||
|
|
||||||
### META folder (SAS9 only)
|
### META folder (SAS9 only)
|
||||||
|
|
||||||
@@ -81,14 +79,14 @@ Macros used in SAS EBI, which connect to the metadata server.
|
|||||||
- OS independent
|
- OS independent
|
||||||
- Metadata aware
|
- Metadata aware
|
||||||
- No X command
|
- No X command
|
||||||
- Prefixes: _mm_
|
- Prefixes: `mm_`
|
||||||
|
|
||||||
### METAX folder (SAS9 only)
|
### METAX folder (SAS9 only)
|
||||||
|
|
||||||
- OS specific
|
- OS specific
|
||||||
- Metadata aware
|
- Metadata aware
|
||||||
- X command enabled
|
- X command enabled
|
||||||
- Prefixes: _mmw_,_mmu_,_mmx_
|
- Prefixes: `mmx_`
|
||||||
|
|
||||||
### SERVER folder (@sasjs/server only)
|
### SERVER folder (@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.
|
These macros are used for building applications using [@sasjs/server](https://server.sasjs.io) - an open source REST API for Desktop SAS.
|
||||||
@@ -96,7 +94,7 @@ These macros are used for building applications using [@sasjs/server](https://se
|
|||||||
- OS independent
|
- OS independent
|
||||||
- @sasjs/server aware
|
- @sasjs/server aware
|
||||||
- No X command
|
- No X command
|
||||||
- Prefixes: _ms_
|
- Prefixes: `ms_`
|
||||||
|
|
||||||
### VIYA folder (Viya only)
|
### VIYA folder (Viya only)
|
||||||
|
|
||||||
@@ -104,7 +102,7 @@ Macros used for interfacing with SAS Viya.
|
|||||||
|
|
||||||
- OS independent
|
- OS independent
|
||||||
- No X command
|
- No X command
|
||||||
- Prefixes: _mv_, _mvf_
|
- Prefixes: `mv_`, `mvf_`
|
||||||
|
|
||||||
### XPLATFORM folder (Viya, Meta, and Server)
|
### XPLATFORM folder (Viya, Meta, and Server)
|
||||||
|
|
||||||
@@ -112,7 +110,7 @@ Sometimes it is helpful to use a macro that can be used interchangeably regardle
|
|||||||
|
|
||||||
- OS independent
|
- OS independent
|
||||||
- No X command
|
- No X command
|
||||||
- Prefixes: _mx_
|
- Prefixes: `mx_`
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -214,7 +212,8 @@ When contributing to this library, it is therefore important to ensure that all
|
|||||||
|
|
||||||
## 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`).
|
- All macros should be compatible with SAS versions from support level B and above (so currently 9.3 and later). If an earlier version is not supported, then the macro should say as such in the header documentation, and exit gracefully.
|
||||||
|
- It's [best to avoid](https://git.datacontroller.io/dc/dc/issues/50) special / non-ASCII characters for compatibility with the widest variety of SAS installations.
|
||||||
|
|
||||||
## Breaking Changes
|
## Breaking Changes
|
||||||
|
|
||||||
@@ -240,13 +239,16 @@ The following repositories are also worth checking out:
|
|||||||
* [chris-swenson/sasmacros](https://github.com/chris-swenson/sasmacros)
|
* [chris-swenson/sasmacros](https://github.com/chris-swenson/sasmacros)
|
||||||
* [greg-wotton/sas-programs](https://github.com/greg-wootton/sas-programs)
|
* [greg-wotton/sas-programs](https://github.com/greg-wootton/sas-programs)
|
||||||
* [KatjaGlassConsulting/SMILE-SmartSASMacros](https://github.com/KatjaGlassConsulting/SMILE-SmartSASMacros)
|
* [KatjaGlassConsulting/SMILE-SmartSASMacros](https://github.com/KatjaGlassConsulting/SMILE-SmartSASMacros)
|
||||||
|
* [paul-canals/toolbox](https://github.com/paul-canals/toolbox)
|
||||||
* [rogerjdeangelis](https://github.com/rogerjdeangelis)
|
* [rogerjdeangelis](https://github.com/rogerjdeangelis)
|
||||||
|
* [SASJedi/sas-macros](https://github.com/SASJedi/sas-macros)
|
||||||
* [scottbass/sas](https://github.com/scottbass/SAS)
|
* [scottbass/sas](https://github.com/scottbass/SAS)
|
||||||
|
* [xieliaing/SAS](https://github.com/xieliaing/SAS)
|
||||||
* [yabwon/sas_packages](https://github.com/yabwon/SAS_PACKAGES)
|
* [yabwon/sas_packages](https://github.com/yabwon/SAS_PACKAGES)
|
||||||
|
|
||||||
## Contributors ✨
|
## Contributors ✨
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
[](#contributors-)
|
[](#contributors-)
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||||
|
|
||||||
@@ -254,22 +256,29 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
<!-- markdownlint-disable -->
|
<!-- markdownlint-disable -->
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tbody>
|
||||||
<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>
|
<tr>
|
||||||
<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" valign="top" width="14.28%"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt="Allan Bowe"/><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/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" valign="top" width="14.28%"><a href="https://github.com/rafgag"><img src="https://avatars.githubusercontent.com/u/69139928?v=4?s=100" width="100px;" alt="rafgag"/><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://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" valign="top" width="14.28%"><a href="https://github.com/tmoody"><img src="https://avatars.githubusercontent.com/u/79837106?v=4?s=100" width="100px;" alt="Trevor Moody"/><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://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" valign="top" width="14.28%"><a href="https://krishna-acondy.io/"><img src="https://avatars.githubusercontent.com/u/2980428?v=4?s=100" width="100px;" alt="Krishna Acondy"/><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://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" valign="top" width="14.28%"><a href="https://github.com/saadjutt01"><img src="https://avatars.githubusercontent.com/u/8914650?v=4?s=100" width="100px;" alt="Muhammad Saad "/><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://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>
|
<td align="center" valign="top" width="14.28%"><a href="https://www.erudicat.com/"><img src="https://avatars.githubusercontent.com/u/25773492?v=4?s=100" width="100px;" alt="Yury Shkoda"/><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>
|
||||||
</tr>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt="Mihajlo Medjedovic"/><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>
|
||||||
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vladislav Parhomchik</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kkchandok"><img src="https://avatars.githubusercontent.com/u/46090627?v=4?s=100" width="100px;" alt="kkchandok"/><br /><sub><b>kkchandok</b></sub></a><br /><a href="#ideas-kkchandok" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||||
<td align="center"><a href="https://github.com/vznesh"><img src="https://avatars.githubusercontent.com/u/28916792?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vignesh T.</b></sub></a><br /><a href="https://github.com/sasjs/core/issues?q=author%3Avznesh" title="Bug reports">🐛</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt="Vladislav Parhomchik"/><br /><sub><b>Vladislav Parhomchik</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
|
||||||
<td align="center"><a href="https://github.com/yabwon"><img src="https://avatars.githubusercontent.com/u/9314894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bart Jablonski</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=yabwon" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vznesh"><img src="https://avatars.githubusercontent.com/u/28916792?v=4?s=100" width="100px;" alt="Vignesh T."/><br /><sub><b>Vignesh T.</b></sub></a><br /><a href="https://github.com/sasjs/core/issues?q=author%3Avznesh" title="Bug reports">🐛</a></td>
|
||||||
<td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=eltociear" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yabwon"><img src="https://avatars.githubusercontent.com/u/9314894?v=4?s=100" width="100px;" alt="Bart Jablonski"/><br /><sub><b>Bart Jablonski</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=yabwon" title="Code">💻</a></td>
|
||||||
</tr>
|
<td align="center" valign="top" width="14.28%"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=100" width="100px;" alt="Ikko Ashimine"/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=eltociear" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/henrik-forsell"><img src="https://avatars.githubusercontent.com/u/109935936?v=4?s=100" width="100px;" alt="Henrik Forsell"/><br /><sub><b>Henrik Forsell</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=henrik-forsell" title="Documentation">📖</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="http://rudvfaden.github.io/"><img src="https://avatars.githubusercontent.com/u/2445577?v=4?s=100" width="100px;" alt="Rud Faden"/><br /><sub><b>Rud Faden</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=rudvfaden" title="Code">💻</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/andyjessen"><img src="https://avatars.githubusercontent.com/u/62343929?v=4?s=100" width="100px;" alt="andyjessen"/><br /><sub><b>andyjessen</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=andyjessen" title="Documentation">📖</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<!-- markdownlint-restore -->
|
<!-- markdownlint-restore -->
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
@brief Abort, ungracefully
|
@brief Abort, ungracefully
|
||||||
@details Will abort with a straightforward %abort if the condition is true.
|
@details Will abort with a straightforward %abort if the condition is true.
|
||||||
|
|
||||||
|
@param [in] mac= (mf_abort.sas) Name of calling macro (is printed to the log)
|
||||||
|
@param [in] msg= ( ) Additional string to print to the log
|
||||||
|
@param [in] iftrue= (%str(1=1)) Conditional logic under which to perform the
|
||||||
|
abort
|
||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
@li mp_abort.sas
|
@li mp_abort.sas
|
||||||
|
|
||||||
@@ -12,7 +17,7 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)
|
%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)
|
||||||
)/*/STORE SOURCE*/;
|
)/des='ungraceful abort' /*STORE SOURCE*/;
|
||||||
|
|
||||||
%if not(%eval(%unquote(&iftrue))) %then %return;
|
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||||
|
|
||||||
@@ -24,4 +29,4 @@
|
|||||||
|
|
||||||
%mend mf_abort;
|
%mend mf_abort;
|
||||||
|
|
||||||
/** @endcond */
|
/** @endcond */
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
%mf_deletefile(&sasjswork/myfile.txt)
|
%mf_deletefile(&sasjswork/myfile.txt)
|
||||||
|
|
||||||
|
|
||||||
@param filepath Full path to the target file
|
@param [in] file Full path to the target file
|
||||||
|
|
||||||
@returns The return code from the fdelete() invocation
|
@returns The return code from the fdelete() invocation
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
expected results (depending on whether you 'expect' the result to be
|
expected results (depending on whether you 'expect' the result to be
|
||||||
case insensitive in this context!)
|
case insensitive in this context!)
|
||||||
|
|
||||||
@param libds library.dataset
|
@param [in] libds library.dataset
|
||||||
@return output returns 1 or 0
|
@return output returns 1 or 0
|
||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
|
|||||||
@@ -40,6 +40,11 @@
|
|||||||
%else %if "&SYSVLONG" < "9.04.01M3" %then 0;
|
%else %if "&SYSVLONG" < "9.04.01M3" %then 0;
|
||||||
%else 1;
|
%else 1;
|
||||||
%end;
|
%end;
|
||||||
|
%else %if &feature=DBMS_MEMTYPE %then %do;
|
||||||
|
/* does dbms_memtype exist in dictionary.tables? */
|
||||||
|
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;
|
||||||
|
%else 1;
|
||||||
|
%end;
|
||||||
%else %if &feature=EXPORTXLS %then %do;
|
%else %if &feature=EXPORTXLS %then %do;
|
||||||
/* is it possible to PROC EXPORT an excel file? */
|
/* is it possible to PROC EXPORT an excel file? */
|
||||||
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 1;
|
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 1;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
@details You can probably do without this macro as it is just a one liner.
|
@details You can probably do without this macro as it is just a one liner.
|
||||||
Mainly it is here as a convenient way to remember the syntax!
|
Mainly it is here as a convenient way to remember the syntax!
|
||||||
|
|
||||||
@param fref the fileref to detect
|
@param [in] fref the fileref to detect
|
||||||
|
|
||||||
@return output Returns 1 if found and 0 if not found. Note - it is possible
|
@return output Returns 1 if found and 0 if not found. Note - it is possible
|
||||||
that the fileref is found, but the file does not (yet) exist. If you need
|
that the fileref is found, but the file does not (yet) exist. If you need
|
||||||
@@ -30,4 +30,4 @@
|
|||||||
0
|
0
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
%mend mf_existfileref;
|
%mend mf_existfileref;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionexists-macro
|
https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionexists-macro
|
||||||
).
|
).
|
||||||
|
|
||||||
@param [in] name (positional) - function name
|
@param [in] name function name
|
||||||
|
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
|
|||||||
@@ -25,13 +25,17 @@
|
|||||||
%local dsid rc;
|
%local dsid rc;
|
||||||
%let dsid=%sysfunc(open(&libds,is));
|
%let dsid=%sysfunc(open(&libds,is));
|
||||||
|
|
||||||
%if &dsid=0 or %length(&var)=0 %then %do;
|
%if &dsid=0 %then %do;
|
||||||
%put %sysfunc(sysmsg());
|
%put %sysfunc(sysmsg());
|
||||||
0
|
0
|
||||||
|
%end;
|
||||||
|
%else %if %length(&var)=0 %then %do;
|
||||||
|
0
|
||||||
|
%let rc=%sysfunc(close(&dsid));
|
||||||
%end;
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
%sysfunc(varnum(&dsid,&var))
|
%sysfunc(varnum(&dsid,&var))
|
||||||
%let rc=%sysfunc(close(&dsid));
|
%let rc=%sysfunc(close(&dsid));
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
%mend mf_existvar;
|
%mend mf_existvar;
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
%put %mf_existVarList(sashelp.class, age sex name dummyvar);
|
%put %mf_existVarList(sashelp.class, age sex name dummyvar);
|
||||||
|
|
||||||
@param libds 2 part dataset or view reference
|
@param [in] libds 2 part dataset or view reference
|
||||||
@param varlist space separated variable names
|
@param [in] varlist space separated variable names
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
%put %mf_getapploc(/some/location/jobs/extract/somejob/);
|
%put %mf_getapploc(/some/location/jobs/extract/somejob/);
|
||||||
%put %mf_getapploc(/some/location/tests/jobs/somejob/);
|
%put %mf_getapploc(/some/location/tests/jobs/somejob/);
|
||||||
|
|
||||||
|
@param [in] pgm The _program value from which to extract the appLoc
|
||||||
|
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
%put Dataset label = %mf_getattrc(sashelp.class,LABEL);
|
%put Dataset label = %mf_getattrc(sashelp.class,LABEL);
|
||||||
%put Member Type = %mf_getattrc(sashelp.class,MTYPE);
|
%put Member Type = %mf_getattrc(sashelp.class,MTYPE);
|
||||||
|
|
||||||
@param libds library.dataset
|
@param [in] libds library.dataset
|
||||||
@param attr full list in [documentation](
|
@param [in] attr full list in [documentation](
|
||||||
https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000147794.htm)
|
https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000147794.htm)
|
||||||
@return output returns result of the attrc value supplied, or -1 and log
|
@return output returns result of the attrc value supplied, or -1 and log
|
||||||
message if err.
|
message if err.
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
%put Number of observations=%mf_getattrn(sashelp.class,NLOBS);
|
%put Number of observations=%mf_getattrn(sashelp.class,NLOBS);
|
||||||
%put Number of variables = %mf_getattrn(sashelp.class,NVARS);
|
%put Number of variables = %mf_getattrn(sashelp.class,NVARS);
|
||||||
|
|
||||||
@param libds library.dataset
|
@param [in] libds library.dataset
|
||||||
@param attr Common values are NLOBS and NVARS, full list in [documentation](
|
@param [in] attr Common values are NLOBS and NVARS, full list in [documentation](
|
||||||
http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212040.htm)
|
http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212040.htm)
|
||||||
@return output returns result of the attrn value supplied, or -1 and log
|
@return output returns result of the attrn value supplied, or -1 and log
|
||||||
message if err.
|
message if err.
|
||||||
|
|||||||
@@ -10,10 +10,9 @@
|
|||||||
|
|
||||||
returns:
|
returns:
|
||||||
|
|
||||||
> DOLLAR $CHAR W MONNAME
|
DOLLAR $CHAR W MONNAME
|
||||||
> $CHAR BEST DOLLAR
|
$CHAR BEST DOLLAR
|
||||||
> BEST Z $CHAR COMMA PERCENTN
|
BEST Z $CHAR COMMA PERCENTN
|
||||||
|
|
||||||
|
|
||||||
@param [in] libds Two part library.dataset reference.
|
@param [in] libds Two part library.dataset reference.
|
||||||
|
|
||||||
|
|||||||
37
base/mf_getgitbranch.sas
Normal file
37
base/mf_getgitbranch.sas
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Retrieves the current branch from a local GIT repo
|
||||||
|
@details In a local git repository, the current branch is always available in
|
||||||
|
the `.git/HEAD` file in a format like this: `ref: refs/heads/master`
|
||||||
|
|
||||||
|
This macro simply reads the file and returns the last word (eg `master`).
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
%let gitdir=%sysfunc(pathname(work))/core;
|
||||||
|
%let repo=https://github.com/sasjs/core;
|
||||||
|
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&gitdir));
|
||||||
|
|
||||||
|
%put The current branch is %mf_getgitbranch(&gitdir);
|
||||||
|
|
||||||
|
@param [in] gitdir The directory containing the GIT repository
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_readfile.sas
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li mp_gitadd.sas
|
||||||
|
@li mp_gitlog.sas
|
||||||
|
@li mp_gitreleaseinfo.sas
|
||||||
|
@li mp_gitstatus.sas
|
||||||
|
|
||||||
|
@version 9.2
|
||||||
|
@author Allan Bowe
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mf_getgitbranch(gitdir
|
||||||
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
%scan(%mf_readfile(&gitdir/.git/HEAD),-1)
|
||||||
|
|
||||||
|
%mend mf_getgitbranch;
|
||||||
@@ -7,8 +7,9 @@
|
|||||||
%put %mf_getkeyvalue(someindex)
|
%put %mf_getkeyvalue(someindex)
|
||||||
|
|
||||||
|
|
||||||
@param key Provide a key on which to perform the lookup
|
@param [in] key Provide a key on which to perform the lookup
|
||||||
@param libds= define the target table which holds the parameters
|
@param [in] libds= (work.mp_setkeyvalue) The library.dataset which holds the
|
||||||
|
parameters
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
@li SASJS
|
@li SASJS
|
||||||
@li BASESAS
|
@li BASESAS
|
||||||
|
|
||||||
@param switch the param for which to return a platform specific variable
|
@param [in] switch the param for which to return a platform specific variable
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_mval.sas
|
@li mf_mval.sas
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
returns:
|
returns:
|
||||||
> dbo
|
> dbo
|
||||||
|
|
||||||
@param libref Library reference (also accepts a 2 level libds ref).
|
@param [in] libref Library reference (also accepts a 2 level libds ref).
|
||||||
|
|
||||||
@return output returns the library schema for the FIRST library encountered
|
@return output returns the library schema for the FIRST library encountered
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
@param [in] prefix= (mclib) first part of the returned libref. As librefs can
|
@param [in] prefix= (mclib) first part of the returned libref. As librefs can
|
||||||
be as long as 8 characters, a maximum length of 7 characters is premitted
|
be as long as 8 characters, a maximum length of 7 characters is premitted
|
||||||
for this prefix.
|
for this prefix.
|
||||||
@param [in] maxtries= Deprecated parameter. Remains here to ensure a
|
@param [in] maxtries= (1000) Deprecated parameter. Remains here to ensure a
|
||||||
non-breaking change. Will be removed in v5.
|
non-breaking change. Will be removed in v5.
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
> MCc59c750610321d4c8bf75faadbcd22
|
> MCc59c750610321d4c8bf75faadbcd22
|
||||||
|
|
||||||
@param prefix= set a prefix for the new name
|
@param [in] prefix= (MC) Sets a prefix for the new name
|
||||||
|
|
||||||
@version 9.3
|
@version 9.3
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|||||||
@@ -13,8 +13,6 @@
|
|||||||
%let user= %mf_getUser();
|
%let user= %mf_getUser();
|
||||||
%put &user;
|
%put &user;
|
||||||
|
|
||||||
@param type - do not use, may be deprecated in a future release
|
|
||||||
|
|
||||||
@return SYSUSERID (if workspace server)
|
@return SYSUSERID (if workspace server)
|
||||||
@return _METAPERSON (if stored process server)
|
@return _METAPERSON (if stored process server)
|
||||||
@return SYS_COMPUTE_SESSION_OWNER (if Viya compute session)
|
@return SYS_COMPUTE_SESSION_OWNER (if Viya compute session)
|
||||||
@@ -23,18 +21,19 @@
|
|||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mf_getuser(type=META
|
%macro mf_getuser(
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%local user metavar;
|
%local user;
|
||||||
%if &type=OS %then %let metavar=_secureusername;
|
|
||||||
%else %let metavar=_metaperson;
|
|
||||||
|
|
||||||
%if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER;
|
%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;
|
||||||
%else %if %symexist(&metavar) %then %do;
|
%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;
|
||||||
%if %length(&&&metavar)=0 %then %let user=&sysuserid;
|
%let user=&SYS_COMPUTE_SESSION_OWNER;
|
||||||
|
%end;
|
||||||
|
%else %if %symexist(_metaperson) %then %do;
|
||||||
|
%if %length(&_metaperson)=0 %then %let user=&sysuserid;
|
||||||
/* sometimes SAS will add @domain extension - remove for consistency */
|
/* sometimes SAS will add @domain extension - remove for consistency */
|
||||||
/* but be sure to quote in case of usernames with commas */
|
/* but be sure to quote in case of usernames with commas */
|
||||||
%else %let user=%unquote(%scan(%quote(&&&metavar),1,@));
|
%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));
|
||||||
%end;
|
%end;
|
||||||
%else %let user=&sysuserid;
|
%else %let user=&sysuserid;
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,9 @@
|
|||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
@li mp_setkeyvalue.sas
|
@li mp_setkeyvalue.sas
|
||||||
|
|
||||||
@param libds dataset to query
|
@param [in] libds dataset to query
|
||||||
@param variable the variable which contains the value to return.
|
@param [in] variable the variable which contains the value to return.
|
||||||
@param filter contents of where clause
|
@param [in] filter= (1) contents of where clause
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|||||||
@@ -25,7 +25,8 @@
|
|||||||
|
|
||||||
@param [in] libds Two part dataset (or view) reference.
|
@param [in] libds Two part dataset (or view) reference.
|
||||||
@param [in] var Variable name for which a format should be returned
|
@param [in] var Variable name for which a format should be returned
|
||||||
@param [in] force=(0) Set to 1 to supply a default if the variable has no format
|
@param [in] force= (0) Set to 1 to supply a default if the variable has no
|
||||||
|
format
|
||||||
@returns outputs format
|
@returns outputs format
|
||||||
|
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
8
|
8
|
||||||
NOTE: Variable renegade does not exist in test
|
NOTE: Variable renegade does not exist in test
|
||||||
|
|
||||||
@param libds Two part dataset (or view) reference.
|
@param [in] libds Two part dataset (or view) reference.
|
||||||
@param var Variable name for which a length should be returned
|
@param [in] var Variable name for which a length should be returned
|
||||||
@returns outputs length
|
@returns outputs length
|
||||||
|
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ returns:
|
|||||||
|
|
||||||
> NOTE: Variable renegade does not exist in test
|
> NOTE: Variable renegade does not exist in test
|
||||||
|
|
||||||
@param libds Two part dataset (or view) reference.
|
@param [in] libds Two part dataset (or view) reference.
|
||||||
@param var Variable name for which a position should be returned
|
@param [in] var Variable name for which a position should be returned
|
||||||
|
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@version 9.2
|
@version 9.2
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ Usage:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@param libds Two part dataset (or view) reference.
|
@param [in] libds Two part dataset (or view) reference.
|
||||||
@param var the variable name to be checked
|
@param [in] var the variable name to be checked
|
||||||
@return output returns C or N depending on variable type. If variable
|
@return output returns C or N depending on variable type. If variable
|
||||||
does not exist then a blank is returned and a note is written to the log.
|
does not exist then a blank is returned and a note is written to the log.
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
returns:
|
returns:
|
||||||
> TEMP
|
> TEMP
|
||||||
|
|
||||||
@param fref The fileref to check
|
@param [in] fref The fileref to check
|
||||||
|
|
||||||
@returns The XENGINE value in sashelp.vextfl or 0 if not found.
|
@returns The XENGINE value in sashelp.vextfl or 0 if not found.
|
||||||
|
|
||||||
|
|||||||
29
base/mf_increment.sas
Normal file
29
base/mf_increment.sas
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Increments a macro variable
|
||||||
|
@details Useful outside of do-loops - will increment a macro variable every
|
||||||
|
time it is called.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
%let cnt=1;
|
||||||
|
%put We have run %mf_increment(cnt) lines;
|
||||||
|
%put Now we have run %mf_increment(cnt) lines;
|
||||||
|
%put There are %mf_increment(cnt) lines in total;
|
||||||
|
|
||||||
|
@param [in] macro_name The name of the macro variable to increment
|
||||||
|
@param [in] incr= (1) The amount to add or subtract to the macro
|
||||||
|
|
||||||
|
<h4> Related Files </h4>
|
||||||
|
@li mf_increment.test.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mf_increment(macro_name,incr=1);
|
||||||
|
|
||||||
|
/* iterate the value */
|
||||||
|
%let ¯o_name=%eval(&&¯o_name+&incr);
|
||||||
|
/* return the value */
|
||||||
|
&&¯o_name
|
||||||
|
|
||||||
|
%mend mf_increment;
|
||||||
@@ -7,12 +7,12 @@
|
|||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
%put mf_isblank(&var);
|
%put %mf_isblank(&var);
|
||||||
|
|
||||||
inspiration:
|
inspiration:
|
||||||
https://support.sas.com/resources/papers/proceedings09/022-2009.pdf
|
https://support.sas.com/resources/papers/proceedings09/022-2009.pdf
|
||||||
|
|
||||||
@param param VALUE to be checked
|
@param [in] Param VALUE to be checked
|
||||||
|
|
||||||
@return output returns 1 (if blank) else 0
|
@return output returns 1 (if blank) else 0
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
With thanks and full credit to Andrea Defronzo -
|
With thanks and full credit to Andrea Defronzo -
|
||||||
https://www.linkedin.com/in/andrea-defronzo-b1a47460/
|
https://www.linkedin.com/in/andrea-defronzo-b1a47460/
|
||||||
|
|
||||||
@param path full path of the file/directory to be checked
|
@param [in] path Full path of the file/directory to be checked
|
||||||
|
|
||||||
@return output returns 1 if path is a directory, 0 if it is not
|
@return output returns 1 if path is a directory, 0 if it is not
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
|
|
||||||
%put %mf_loc(POF); %*location of PlatformObjectFramework tools;
|
%put %mf_loc(POF); %*location of PlatformObjectFramework tools;
|
||||||
|
|
||||||
|
@param [in] loc The item to locate, eg:
|
||||||
|
@li PLAATFORMOBJECTFRAMEWORK (or POF)
|
||||||
|
@li VIYACONFG
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
@@ -15,7 +19,8 @@
|
|||||||
%local root;
|
%local root;
|
||||||
|
|
||||||
%if &loc=POF or &loc=PLATFORMOBJECTFRAMEWORK %then %do;
|
%if &loc=POF or &loc=PLATFORMOBJECTFRAMEWORK %then %do;
|
||||||
%let root=%substr(%sysget(SASROOT),1,%index(%sysget(SASROOT),SASFoundation)-2);
|
%let root=%sysget(SASROOT);
|
||||||
|
%let root=%substr(&root,1,%index(&root,SASFoundation)-2);
|
||||||
%let root=&root/SASPlatformObjectFramework/&sysver;
|
%let root=&root/SASPlatformObjectFramework/&sysver;
|
||||||
%put Batch tools located at: &root;
|
%put Batch tools located at: &root;
|
||||||
&root
|
&root
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Usage:
|
|||||||
%mf_mkdir(/some/path/name)
|
%mf_mkdir(/some/path/name)
|
||||||
|
|
||||||
|
|
||||||
@param dir relative or absolute pathname. Unquoted.
|
@param [in] dir Relative or absolute pathname. Unquoted.
|
||||||
@version 9.2
|
@version 9.2
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
%if %mf_mval(maynotexist)=itdid %then %do;
|
%if %mf_mval(maynotexist)=itdid %then %do;
|
||||||
|
|
||||||
|
@param [in] var The macro variable NAME to return the (possible) value for
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_getattrn.sas
|
@li mf_getattrn.sas
|
||||||
|
|
||||||
@param libds library.dataset
|
@param [in] libds library.dataset
|
||||||
|
|
||||||
@return output returns result of the attrn value supplied, or log message
|
@return output returns result of the attrn value supplied, or log message
|
||||||
if err.
|
if err.
|
||||||
|
|||||||
63
base/mf_readfile.sas
Normal file
63
base/mf_readfile.sas
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Reads the first line of a file using pure macro
|
||||||
|
@details Reads the first line of a file and returns it. Future versions may
|
||||||
|
read each line into a macro variable array.
|
||||||
|
|
||||||
|
Generally, reading data into macro variables is not great as certain
|
||||||
|
nonprintable characters (such as CR, LF) may be dropped in the conversion.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
%mf_writefile(&sasjswork/myfile.txt,l1=some content,l2=more content)
|
||||||
|
|
||||||
|
%put %mf_readfile(&sasjswork/myfile.txt);
|
||||||
|
|
||||||
|
|
||||||
|
@param [in] fpath Full path to file to be read
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li mf_deletefile.sas
|
||||||
|
@li mf_writefile.sas
|
||||||
|
@li mf_readfile.test.sas
|
||||||
|
|
||||||
|
@version 9.2
|
||||||
|
@author Allan Bowe
|
||||||
|
**/
|
||||||
|
/** @cond */
|
||||||
|
|
||||||
|
%macro mf_readfile(fpath
|
||||||
|
)/*/STORE SOURCE*/;
|
||||||
|
%local fref rc fid fcontent;
|
||||||
|
|
||||||
|
/* check file exists */
|
||||||
|
%if %sysfunc(filename(fref,&fpath)) ne 0 %then %do;
|
||||||
|
%put &=fref &=fpath;
|
||||||
|
%put %str(ERR)OR: %sysfunc(sysmsg());
|
||||||
|
%return;
|
||||||
|
%end;
|
||||||
|
|
||||||
|
%let fid=%sysfunc(fopen(&fref,I));
|
||||||
|
|
||||||
|
%if &fid=0 %then %do;
|
||||||
|
%put %str(ERR)OR: %sysfunc(sysmsg());
|
||||||
|
%return;
|
||||||
|
%end;
|
||||||
|
|
||||||
|
%if %sysfunc(fread(&fid)) = 0 %then %do;
|
||||||
|
%let rc=%sysfunc(fget(&fid,fcontent,65534));
|
||||||
|
&fcontent
|
||||||
|
%end;
|
||||||
|
|
||||||
|
/*
|
||||||
|
%do %while(%sysfunc(fread(&fid)) = 0);
|
||||||
|
%let rc=%sysfunc(fget(&fid,fcontent,65534));
|
||||||
|
&fcontent
|
||||||
|
%end;
|
||||||
|
*/
|
||||||
|
|
||||||
|
%let rc=%sysfunc(fclose(&fid));
|
||||||
|
%let rc=%sysfunc(filename(&fref));
|
||||||
|
|
||||||
|
%mend mf_readfile;
|
||||||
|
/** @endcond */
|
||||||
@@ -11,8 +11,8 @@
|
|||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
|
|
||||||
|
|
||||||
@param basestr The string to be modified
|
@param [in] basestr The string to be modified
|
||||||
@param trimstr The string to be removed from the end of `basestr`, if it
|
@param [in] trimstr The string to be removed from the end of `basestr`, if it
|
||||||
exists
|
exists
|
||||||
|
|
||||||
@return output returns result with the value of `trimstr` removed from the end
|
@return output returns result with the value of `trimstr` removed from the end
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
returns:
|
returns:
|
||||||
> blah blaaah brah
|
> blah blaaah brah
|
||||||
|
|
||||||
@param str1= string containing words to extract
|
@param [in] str1= () string containing words to extract
|
||||||
@param str2= used to compare with the extract string
|
@param [in] str2= () used to compare with the extract string
|
||||||
|
|
||||||
@warning CASE SENSITIVE!
|
@warning CASE SENSITIVE!
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
returns:
|
returns:
|
||||||
> sss bram boo
|
> sss bram boo
|
||||||
|
|
||||||
@param [in] str1= string containing words to extract
|
@param [in] str1= () String containing words to extract
|
||||||
@param [in] str2= used to compare with the extract string
|
@param [in] str2= () Used to compare with the extract string
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
@param [in] mode= (O) Available options are A or O as follows:
|
@param [in] mode= (O) Available options are A or O as follows:
|
||||||
@li A APPEND mode, writes new records after the current end of the file.
|
@li A APPEND mode, writes new records after the current end of the file.
|
||||||
@li O OUTPUT mode, writes new records from the beginning of the file.
|
@li O OUTPUT mode, writes new records from the beginning of the file.
|
||||||
@param [in] l1= First line
|
@param [in] l1= () First line
|
||||||
@param [in] l2= Second line (etc through to l10)
|
@param [in] l2= () Second line (etc through to l10)
|
||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
@li mf_writefile.test.sas
|
@li mf_writefile.test.sas
|
||||||
|
|||||||
@@ -8,8 +8,10 @@
|
|||||||
|
|
||||||
The method used varies according to the context. Important points:
|
The method used varies according to the context. Important points:
|
||||||
|
|
||||||
@li should not use endsas or abort cancel in 9.4m3 environments as this can
|
@li should not use endsas or abort cancel in 9.4m3 WIN environments as this
|
||||||
cause hung multibridge sessions and result in a frozen STP server
|
can cause hung multibridge sessions and result in a frozen STP server
|
||||||
|
@li The use of endsas in 9.4m6+ windows environments for POST requests to the
|
||||||
|
STP server can result in an empty response body
|
||||||
@li should not use endsas in viya 3.5 as this destroys the session and cannot
|
@li should not use endsas in viya 3.5 as this destroys the session and cannot
|
||||||
fetch results (although both mv_getjoblog.sas and the @sasjs/adapter will
|
fetch results (although both mv_getjoblog.sas and the @sasjs/adapter will
|
||||||
recognise this and fetch the log of the parent session instead)
|
recognise this and fetch the log of the parent session instead)
|
||||||
@@ -21,18 +23,19 @@
|
|||||||
a macro. For that, we recommend you use mp_include.sas to perform the
|
a macro. For that, we recommend you use mp_include.sas to perform the
|
||||||
include, and then call \%mp_abort(mode=INCLUDE) from the source program (ie,
|
include, and then call \%mp_abort(mode=INCLUDE) from the source program (ie,
|
||||||
OUTSIDE of the top-parent macro).
|
OUTSIDE of the top-parent macro).
|
||||||
The soft abort has also been found to be ineffective in 9.4m6 windows
|
The soft abort has become ineffective in 9.4m6 WINDOWS environments. We are
|
||||||
environments and above, so in these cases, endsas is used.
|
currently investigating approaches to deal with this.
|
||||||
|
|
||||||
|
|
||||||
@param mac= (mp_abort.sas) To contain the name of the calling macro. Do not
|
@param [in] mac= (mp_abort.sas) To contain the name of the calling macro. Do
|
||||||
use &sysmacroname as this will always resolve to MP_ABORT.
|
not use &sysmacroname as this will always resolve to MP_ABORT.
|
||||||
@param msg= message to be returned
|
@param [out] msg= message to be returned
|
||||||
@param iftrue= (1=1) Supply a condition for which the macro should be executed
|
@param [in] iftrue= (1=1) Condition under which the macro should be executed
|
||||||
@param errds= (work.mp_abort_errds) There is no clean way to end a process
|
@param [in] errds= (work.mp_abort_errds) There is no clean way to end a
|
||||||
within a %include called within a macro. Furthermore, there is no way to
|
process within a %include called within a macro. Furthermore, there is no
|
||||||
test if a macro is called within a %include. To handle this particular
|
way to test if a macro is called within a %include. To handle this
|
||||||
scenario, the %include should be switched for the mp_include.sas macro.
|
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
|
This provides an indicator that we are running a macro within a \%include
|
||||||
(`_SYSINCLUDEFILEDEVICE`) and allows us to provide a dataset with the abort
|
(`_SYSINCLUDEFILEDEVICE`) and allows us to provide a dataset with the abort
|
||||||
values (msg, mac).
|
values (msg, mac).
|
||||||
@@ -43,8 +46,8 @@
|
|||||||
@li msg (the message)
|
@li msg (the message)
|
||||||
@li mac (the mac param)
|
@li mac (the mac param)
|
||||||
|
|
||||||
@param mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked for
|
@param [in] mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked
|
||||||
an abort status.
|
for an abort status.
|
||||||
Valid values:
|
Valid values:
|
||||||
@li REGULAR (default)
|
@li REGULAR (default)
|
||||||
@li INCLUDE
|
@li INCLUDE
|
||||||
@@ -63,236 +66,273 @@
|
|||||||
, mode=REGULAR
|
, mode=REGULAR
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc;
|
%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;
|
||||||
%local fref fid i;
|
%local fref fid i;
|
||||||
|
|
||||||
%if not(%eval(%unquote(&iftrue))) %then %return;
|
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||||
|
|
||||||
%put NOTE: /// mp_abort macro executing //;
|
%put NOTE: /// mp_abort macro executing //;
|
||||||
%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)
|
%if %symexist(_SYSINCLUDEFILEDEVICE)
|
||||||
/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */
|
/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */
|
||||||
and "&SYSPROCESSNAME " ne "Compute Server "
|
and %superq(SYSPROCESSNAME) ne %str(Compute Server)
|
||||||
|
%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;
|
||||||
|
|
||||||
|
/* Web App Context */
|
||||||
|
%if %symexist(_PROGRAM)
|
||||||
|
or %superq(SYSPROCESSNAME) = %str(Compute Server)
|
||||||
|
or &mode=INCLUDE
|
||||||
|
%then %do;
|
||||||
|
options obs=max replace mprint;
|
||||||
|
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"
|
||||||
%then %do;
|
%then %do;
|
||||||
%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
|
options nosyntaxcheck;
|
||||||
data &errds;
|
%end;
|
||||||
iftrue='1=1';
|
|
||||||
length mac $100 msg $5000;
|
%if &mode=INCLUDE %then %do;
|
||||||
mac=symget('mac');
|
%if %sysfunc(exist(&errds))=1 %then %do;
|
||||||
msg=symget('msg');
|
|
||||||
run;
|
|
||||||
data _null_;
|
data _null_;
|
||||||
abort cancel FILE;
|
set &errds;
|
||||||
|
call symputx('iftrue',iftrue,'l');
|
||||||
|
call symputx('mac',mac,'l');
|
||||||
|
call symputx('msg',msg,'l');
|
||||||
|
putlog (_all_)(=);
|
||||||
run;
|
run;
|
||||||
|
%if (&iftrue)=0 %then %return;
|
||||||
|
%end;
|
||||||
|
%else %do;
|
||||||
|
%put &sysmacroname: No include errors found;
|
||||||
%return;
|
%return;
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
/* Web App Context */
|
/* extract log errs / warns, if exist */
|
||||||
%if %symexist(_PROGRAM)
|
%local logloc logline;
|
||||||
or "&SYSPROCESSNAME "="Compute Server "
|
%global logmsg; /* capture global messages */
|
||||||
or &mode=INCLUDE
|
%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;
|
||||||
%then %do;
|
%else %let logloc=%qsysfunc(getoption(LOG));
|
||||||
options obs=max replace mprint;
|
proc printto log=log;run;
|
||||||
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"
|
%let logline=0;
|
||||||
%then %do;
|
%if %length(&logloc)>0 %then %do;
|
||||||
options nosyntaxcheck;
|
data _null_;
|
||||||
%end;
|
infile &logloc lrecl=5000;
|
||||||
|
input; putlog _infile_;
|
||||||
%if &mode=INCLUDE %then %do;
|
i=1;
|
||||||
%if %sysfunc(exist(&errds))=1 %then %do;
|
retain logonce 0;
|
||||||
data _null_;
|
if (
|
||||||
set &errds;
|
_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
|
||||||
call symputx('iftrue',iftrue,'l');
|
) and logonce=0 then
|
||||||
call symputx('mac',mac,'l');
|
do;
|
||||||
call symputx('msg',msg,'l');
|
call symputx('logline',_n_);
|
||||||
putlog (_all_)(=);
|
logonce+1;
|
||||||
run;
|
end;
|
||||||
%if (&iftrue)=0 %then %return;
|
run;
|
||||||
%end;
|
/* capture log including lines BEFORE the err */
|
||||||
%else %do;
|
%if &logline>0 %then %do;
|
||||||
%put &sysmacroname: No include errors found;
|
|
||||||
%return;
|
|
||||||
%end;
|
|
||||||
%end;
|
|
||||||
|
|
||||||
/* extract log errs / warns, if exist */
|
|
||||||
%local logloc logline;
|
|
||||||
%global logmsg; /* capture global messages */
|
|
||||||
%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;
|
|
||||||
%else %let logloc=%qsysfunc(getoption(LOG));
|
|
||||||
proc printto log=log;run;
|
|
||||||
%let logline=0;
|
|
||||||
%if %length(&logloc)>0 %then %do;
|
|
||||||
data _null_;
|
data _null_;
|
||||||
infile &logloc lrecl=5000;
|
infile &logloc lrecl=5000;
|
||||||
input; putlog _infile_;
|
input;
|
||||||
i=1;
|
i=1;
|
||||||
retain logonce 0;
|
stoploop=0;
|
||||||
if (
|
if _n_ ge &logline-15 and stoploop=0 then do until (i>22);
|
||||||
_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
|
call symputx('logmsg',catx('\n',symget('logmsg'),_infile_));
|
||||||
) and logonce=0 then
|
|
||||||
do;
|
|
||||||
call symputx('logline',_n_);
|
|
||||||
logonce+1;
|
|
||||||
end;
|
|
||||||
run;
|
|
||||||
/* capture log including lines BEFORE the err */
|
|
||||||
%if &logline>0 %then %do;
|
|
||||||
data _null_;
|
|
||||||
infile &logloc lrecl=5000;
|
|
||||||
input;
|
input;
|
||||||
i=1;
|
i+1;
|
||||||
stoploop=0;
|
stoploop=1;
|
||||||
if _n_ ge &logline-15 and stoploop=0 then do until (i>22);
|
end;
|
||||||
call symputx('logmsg',catx('\n',symget('logmsg'),_infile_));
|
if stoploop=1 then stop;
|
||||||
input;
|
|
||||||
i+1;
|
|
||||||
stoploop=1;
|
|
||||||
end;
|
|
||||||
if stoploop=1 then stop;
|
|
||||||
run;
|
|
||||||
%end;
|
|
||||||
%end;
|
|
||||||
|
|
||||||
%if %symexist(SYS_JES_JOB_URI) %then %do;
|
|
||||||
/* setup webout for Viya */
|
|
||||||
options nobomfile;
|
|
||||||
%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
|
|
||||||
filename _webout temp lrecl=999999 mod;
|
|
||||||
%end;
|
|
||||||
%else %do;
|
|
||||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"
|
|
||||||
name="_webout.json" lrecl=999999 mod;
|
|
||||||
%end;
|
|
||||||
%end;
|
|
||||||
%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;
|
|
||||||
options nobomfile;
|
|
||||||
/* set up http header for SASjs Server */
|
|
||||||
%let fid=%sysfunc(fopen(&fref,A));
|
|
||||||
%if &fid=0 %then %do;
|
|
||||||
%put %str(ERR)OR: %sysfunc(sysmsg());
|
|
||||||
%return;
|
|
||||||
%end;
|
|
||||||
%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));
|
|
||||||
%let rc=%sysfunc(fwrite(&fid));
|
|
||||||
%let rc=%sysfunc(fclose(&fid));
|
|
||||||
%let rc=%sysfunc(filename(&fref));
|
|
||||||
%end;
|
|
||||||
|
|
||||||
/* send response in SASjs JSON format */
|
|
||||||
data _null_;
|
|
||||||
file _webout mod lrecl=32000 encoding='utf-8';
|
|
||||||
length msg syswarningtext syserrortext $32767 mode $10 ;
|
|
||||||
sasdatetime=datetime();
|
|
||||||
msg=symget('msg');
|
|
||||||
%if &logline>0 %then %do;
|
|
||||||
msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
|
|
||||||
%end;
|
|
||||||
/* escape the escapes */
|
|
||||||
msg=tranwrd(msg,'\','\\');
|
|
||||||
/* escape the quotes */
|
|
||||||
msg=tranwrd(msg,'"','\"');
|
|
||||||
/* ditch the CRLFs as chrome complains */
|
|
||||||
msg=compress(msg,,'kw');
|
|
||||||
/* quote without quoting the quotes (which are escaped instead) */
|
|
||||||
msg=cats('"',msg,'"');
|
|
||||||
if symexist('_debug') then debug=quote(trim(symget('_debug')));
|
|
||||||
else debug='""';
|
|
||||||
if symget('sasjsprocessmode')='Stored Program' then mode='SASJS';
|
|
||||||
if mode ne 'SASJS' then put '>>weboutBEGIN<<';
|
|
||||||
put '{"SYSDATE" : "' "&SYSDATE" '"';
|
|
||||||
put ',"SYSTIME" : "' "&SYSTIME" '"';
|
|
||||||
put ',"sasjsAbort" : [{';
|
|
||||||
put ' "MSG":' msg ;
|
|
||||||
put ' ,"MAC": "' "&mac" '"}]';
|
|
||||||
put ",""SYSUSERID"" : ""&sysuserid"" ";
|
|
||||||
put ',"_DEBUG":' debug ;
|
|
||||||
if symexist('_metauser') then do;
|
|
||||||
_METAUSER=quote(trim(symget('_METAUSER')));
|
|
||||||
put ",""_METAUSER"": " _METAUSER;
|
|
||||||
_METAPERSON=quote(trim(symget('_METAPERSON')));
|
|
||||||
put ',"_METAPERSON": ' _METAPERSON;
|
|
||||||
end;
|
|
||||||
if symexist('SYS_JES_JOB_URI') then do;
|
|
||||||
SYS_JES_JOB_URI=quote(trim(symget('SYS_JES_JOB_URI')));
|
|
||||||
put ',"SYS_JES_JOB_URI": ' SYS_JES_JOB_URI;
|
|
||||||
end;
|
|
||||||
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
|
||||||
put ',"_PROGRAM" : ' _PROGRAM ;
|
|
||||||
put ",""SYSCC"" : ""&syscc"" ";
|
|
||||||
syserrortext=quote(trim(symget('syserrortext')));
|
|
||||||
put ",""SYSERRORTEXT"" : " syserrortext;
|
|
||||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
|
||||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
|
||||||
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
|
||||||
put ",""SYSSITE"" : ""&syssite"" ";
|
|
||||||
sysvlong=quote(trim(symget('sysvlong')));
|
|
||||||
put ',"SYSVLONG" : ' sysvlong;
|
|
||||||
syswarningtext=quote(trim(symget('syswarningtext')));
|
|
||||||
put ",""SYSWARNINGTEXT"" : " syswarningtext;
|
|
||||||
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
|
|
||||||
put "}" ;
|
|
||||||
if mode ne 'SASJS' then put '>>weboutEND<<';
|
|
||||||
run;
|
|
||||||
|
|
||||||
%put _all_;
|
|
||||||
|
|
||||||
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
|
|
||||||
data _null_;
|
|
||||||
putlog 'stpsrvset program err and syscc';
|
|
||||||
rc=stpsrvset('program error', 0);
|
|
||||||
call symputx("syscc",0,"g");
|
|
||||||
run;
|
run;
|
||||||
%if &sysscp=WIN
|
|
||||||
and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"
|
|
||||||
and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;
|
|
||||||
/* skip approach (below) does not work in windows m6+ envs */
|
|
||||||
endsas;
|
|
||||||
%end;
|
|
||||||
%else %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
|
|
||||||
* 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;
|
%end;
|
||||||
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
|
%end;
|
||||||
/* endsas kills the session making it harder to fetch results */
|
|
||||||
data _null_;
|
%if %symexist(SYS_JES_JOB_URI) %then %do;
|
||||||
syswarningtext=symget('syswarningtext');
|
/* setup webout for Viya */
|
||||||
syserrortext=symget('syserrortext');
|
options nobomfile;
|
||||||
abort_msg=symget('msg');
|
%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
|
||||||
syscc=symget('syscc');
|
filename _webout temp lrecl=999999 mod;
|
||||||
sysuserid=symget('sysuserid');
|
|
||||||
iftrue=symget('iftrue');
|
|
||||||
put (_all_)(/=);
|
|
||||||
call symputx('syscc',0);
|
|
||||||
abort cancel nolist;
|
|
||||||
run;
|
|
||||||
%end;
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
%abort cancel;
|
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"
|
||||||
|
name="_webout.json" lrecl=999999 mod;
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
|
%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;
|
||||||
|
options nobomfile;
|
||||||
|
/* set up http header for SASjs Server */
|
||||||
|
%let fid=%sysfunc(fopen(&fref,A));
|
||||||
|
%if &fid=0 %then %do;
|
||||||
|
%put %str(ERR)OR: %sysfunc(sysmsg());
|
||||||
|
%return;
|
||||||
|
%end;
|
||||||
|
%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));
|
||||||
|
%let rc=%sysfunc(fwrite(&fid));
|
||||||
|
%let rc=%sysfunc(fclose(&fid));
|
||||||
|
%let rc=%sysfunc(filename(&fref));
|
||||||
|
%end;
|
||||||
|
|
||||||
|
/* send response in SASjs JSON format */
|
||||||
|
data _null_;
|
||||||
|
file _webout mod lrecl=32000 encoding='utf-8';
|
||||||
|
length msg syswarningtext syserrortext $32767 mode $10 ;
|
||||||
|
sasdatetime=datetime();
|
||||||
|
msg=symget('msg');
|
||||||
|
%if &logline>0 %then %do;
|
||||||
|
msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
|
||||||
|
%end;
|
||||||
|
/* escape the escapes */
|
||||||
|
msg=tranwrd(msg,'\','\\');
|
||||||
|
/* escape the quotes */
|
||||||
|
msg=tranwrd(msg,'"','\"');
|
||||||
|
/* ditch the CRLFs as chrome complains */
|
||||||
|
msg=compress(msg,,'kw');
|
||||||
|
/* quote without quoting the quotes (which are escaped instead) */
|
||||||
|
msg=cats('"',msg,'"');
|
||||||
|
if symexist('_debug') then debug=quote(trim(symget('_debug')));
|
||||||
|
else debug='""';
|
||||||
|
if symget('sasjsprocessmode')='Stored Program' then mode='SASJS';
|
||||||
|
if mode ne 'SASJS' then put '>>weboutBEGIN<<';
|
||||||
|
put '{"SYSDATE" : "' "&SYSDATE" '"';
|
||||||
|
put ',"SYSTIME" : "' "&SYSTIME" '"';
|
||||||
|
put ',"sasjsAbort" : [{';
|
||||||
|
put ' "MSG":' msg ;
|
||||||
|
put ' ,"MAC": "' "&mac" '"}]';
|
||||||
|
put ",""SYSUSERID"" : ""&sysuserid"" ";
|
||||||
|
put ',"_DEBUG":' debug ;
|
||||||
|
if symexist('_metauser') then do;
|
||||||
|
_METAUSER=quote(trim(symget('_METAUSER')));
|
||||||
|
put ",""_METAUSER"": " _METAUSER;
|
||||||
|
_METAPERSON=quote(trim(symget('_METAPERSON')));
|
||||||
|
put ',"_METAPERSON": ' _METAPERSON;
|
||||||
|
end;
|
||||||
|
if symexist('SYS_JES_JOB_URI') then do;
|
||||||
|
SYS_JES_JOB_URI=quote(trim(symget('SYS_JES_JOB_URI')));
|
||||||
|
put ',"SYS_JES_JOB_URI": ' SYS_JES_JOB_URI;
|
||||||
|
end;
|
||||||
|
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
||||||
|
put ',"_PROGRAM" : ' _PROGRAM ;
|
||||||
|
put ",""SYSCC"" : ""&syscc"" ";
|
||||||
|
syserrortext=cats(symget('syserrortext'));
|
||||||
|
if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do;
|
||||||
|
syserrortext='"'!!trim(
|
||||||
|
prxchange('s/"/\\"/',-1, /* double quote */
|
||||||
|
prxchange('s/\x0A/\n/',-1, /* new line */
|
||||||
|
prxchange('s/\x0D/\r/',-1, /* carriage return */
|
||||||
|
prxchange('s/\x09/\\t/',-1, /* tab */
|
||||||
|
prxchange('s/\x00/\\u0000/',-1, /* NUL */
|
||||||
|
prxchange('s/\x0E/\\u000E/',-1, /* SS */
|
||||||
|
prxchange('s/\x0F/\\u000F/',-1, /* SF */
|
||||||
|
prxchange('s/\x01/\\u0001/',-1, /* SOH */
|
||||||
|
prxchange('s/\x02/\\u0002/',-1, /* STX */
|
||||||
|
prxchange('s/\x10/\\u0010/',-1, /* DLE */
|
||||||
|
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
|
||||||
|
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
|
||||||
|
prxchange('s/\\/\\\\/',-1,syserrortext)
|
||||||
|
)))))))))))))!!'"';
|
||||||
|
end;
|
||||||
|
else syserrortext=cats('"',syserrortext,'"');
|
||||||
|
put ',"SYSERRORTEXT" : ' syserrortext;
|
||||||
|
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||||
|
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||||
|
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||||
|
put ",""SYSSITE"" : ""&syssite"" ";
|
||||||
|
sysvlong=quote(trim(symget('sysvlong')));
|
||||||
|
put ',"SYSVLONG" : ' sysvlong;
|
||||||
|
syswarningtext=cats(symget('syswarningtext'));
|
||||||
|
if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do;
|
||||||
|
syswarningtext='"'!!trim(
|
||||||
|
prxchange('s/"/\\"/',-1, /* double quote */
|
||||||
|
prxchange('s/\x0A/\n/',-1, /* new line */
|
||||||
|
prxchange('s/\x0D/\r/',-1, /* carriage return */
|
||||||
|
prxchange('s/\x09/\\t/',-1, /* tab */
|
||||||
|
prxchange('s/\x00/\\u0000/',-1, /* NUL */
|
||||||
|
prxchange('s/\x0E/\\u000E/',-1, /* SS */
|
||||||
|
prxchange('s/\x0F/\\u000F/',-1, /* SF */
|
||||||
|
prxchange('s/\x01/\\u0001/',-1, /* SOH */
|
||||||
|
prxchange('s/\x02/\\u0002/',-1, /* STX */
|
||||||
|
prxchange('s/\x10/\\u0010/',-1, /* DLE */
|
||||||
|
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
|
||||||
|
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
|
||||||
|
prxchange('s/\\/\\\\/',-1,syswarningtext)
|
||||||
|
)))))))))))))!!'"';
|
||||||
|
end;
|
||||||
|
else syswarningtext=cats('"',syswarningtext,'"');
|
||||||
|
put ",""SYSWARNINGTEXT"" : " syswarningtext;
|
||||||
|
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
|
||||||
|
put "}" ;
|
||||||
|
if mode ne 'SASJS' then put '>>weboutEND<<';
|
||||||
|
run;
|
||||||
|
|
||||||
|
%put _all_;
|
||||||
|
|
||||||
|
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
|
||||||
|
data _null_;
|
||||||
|
putlog 'stpsrvset program err and syscc';
|
||||||
|
rc=stpsrvset('program error', 0);
|
||||||
|
call symputx("syscc",0,"g");
|
||||||
|
run;
|
||||||
|
%if &sysscp=WIN
|
||||||
|
and 1=0 /* deprecating this logic until we figure out a consistent abort */
|
||||||
|
and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"
|
||||||
|
and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;
|
||||||
|
/* skip approach (below) does not work in windows m6+ envs */
|
||||||
|
endsas;
|
||||||
|
%end;
|
||||||
|
%else %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
|
||||||
|
* 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;
|
||||||
|
/* endsas kills the session making it harder to fetch results */
|
||||||
|
data _null_;
|
||||||
|
syswarningtext=symget('syswarningtext');
|
||||||
|
syserrortext=symget('syserrortext');
|
||||||
|
abort_msg=symget('msg');
|
||||||
|
syscc=symget('syscc');
|
||||||
|
sysuserid=symget('sysuserid');
|
||||||
|
iftrue=symget('iftrue');
|
||||||
|
put (_all_)(/=);
|
||||||
|
call symputx('syscc',0);
|
||||||
|
abort cancel nolist;
|
||||||
|
run;
|
||||||
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
%put _all_;
|
|
||||||
%abort cancel;
|
%abort cancel;
|
||||||
%end;
|
%end;
|
||||||
|
%end;
|
||||||
|
%else %do;
|
||||||
|
%put _all_;
|
||||||
|
%abort cancel;
|
||||||
|
%end;
|
||||||
%mend mp_abort;
|
%mend mp_abort;
|
||||||
|
|
||||||
/** @endcond */
|
/** @endcond */
|
||||||
|
|||||||
95
base/mp_aligndecimal.sas
Normal file
95
base/mp_aligndecimal.sas
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Apply leading blanks to align numbers vertically in a char variable
|
||||||
|
@details This is particularly useful when storing numbers (as character) that
|
||||||
|
need to be sorted.
|
||||||
|
|
||||||
|
It works by splitting the number left and right of the decimal place, and
|
||||||
|
aligning it accordingly. A temporary variable is created as part of this
|
||||||
|
process (which is automatically dropped)
|
||||||
|
|
||||||
|
The macro can be used only in data step, eg as follows:
|
||||||
|
|
||||||
|
data _null_;
|
||||||
|
length myvar $50;
|
||||||
|
do i=1 to 1000 by 50;
|
||||||
|
if mod(i,2)=0 then j=ranuni(0)*i*100;
|
||||||
|
else j=i*100;
|
||||||
|
|
||||||
|
%mp_aligndecimal(myvar,width=7)
|
||||||
|
|
||||||
|
leading_spaces=length(myvar)-length(cats(myvar));
|
||||||
|
putlog +leading_spaces myvar;
|
||||||
|
end;
|
||||||
|
run;
|
||||||
|
|
||||||
|
The generated code will look something like this:
|
||||||
|
|
||||||
|
length aligndp4e49996 $7;
|
||||||
|
if index(myvar,'.') then do;
|
||||||
|
aligndp4e49996=cats(scan(myvar,1,'.'));
|
||||||
|
aligndp4e49996=right(aligndp4e49996);
|
||||||
|
myvar=aligndp4e49996!!'.'!!cats(scan(myvar,2,'.'));
|
||||||
|
end;
|
||||||
|
else do;
|
||||||
|
aligndp4e49996=myvar;
|
||||||
|
aligndp4e49996=right(aligndp4e49996);
|
||||||
|
myvar=aligndp4e49996;
|
||||||
|
end;
|
||||||
|
drop aligndp4e49996;
|
||||||
|
|
||||||
|
Results (myvar variable):
|
||||||
|
|
||||||
|
0.7683559324
|
||||||
|
122.8232796
|
||||||
|
99419.50552
|
||||||
|
42938.5143414
|
||||||
|
763.3799189
|
||||||
|
15170.606073
|
||||||
|
15083.285773
|
||||||
|
85443.198707
|
||||||
|
2022999.2251
|
||||||
|
12038.658867
|
||||||
|
1350582.6734
|
||||||
|
52777.258221
|
||||||
|
11723.347628
|
||||||
|
33101.268376
|
||||||
|
6181622.8603
|
||||||
|
7390614.0669
|
||||||
|
73384.537893
|
||||||
|
1788362.1016
|
||||||
|
2774586.2219
|
||||||
|
7998580.8415
|
||||||
|
|
||||||
|
|
||||||
|
@param [in] var The (data step, character) variable to modify
|
||||||
|
@param [in] width= (8) The number of characters BEFORE the decimal point
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_getuniquename.sas
|
||||||
|
|
||||||
|
<h4> Related Programs </h4>
|
||||||
|
@li mp_aligndecimal.test.sas
|
||||||
|
|
||||||
|
@version 9.2
|
||||||
|
@author Allan Bowe
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_aligndecimal(var,width=8);
|
||||||
|
|
||||||
|
%local tmpvar;
|
||||||
|
%let tmpvar=%mf_getuniquename(prefix=aligndp);
|
||||||
|
length &tmpvar $&width;
|
||||||
|
if index(&var,'.') then do;
|
||||||
|
&tmpvar=cats(scan(&var,1,'.'));
|
||||||
|
&tmpvar=right(&tmpvar);
|
||||||
|
&var=&tmpvar!!'.'!!cats(scan(&var,2,'.'));
|
||||||
|
end;
|
||||||
|
else do;
|
||||||
|
&tmpvar=cats(&var);
|
||||||
|
&tmpvar=right(&tmpvar);
|
||||||
|
&var=&tmpvar;
|
||||||
|
end;
|
||||||
|
drop &tmpvar;
|
||||||
|
|
||||||
|
%mend mp_aligndecimal;
|
||||||
@@ -17,8 +17,8 @@
|
|||||||
%mp_appendfile(baseref=tmp1, appendrefs=tmp2 tmp3)
|
%mp_appendfile(baseref=tmp1, appendrefs=tmp2 tmp3)
|
||||||
|
|
||||||
|
|
||||||
@param [in] baseref= Fileref of the base file (should exist)
|
@param [in] baseref= (0) Fileref of the base file (should exist)
|
||||||
@param [in] appendrefs= One or more filerefs to be appended to the base
|
@param [in] appendrefs= (0) One or more filerefs to be appended to the base
|
||||||
fileref. Space separated.
|
fileref. Space separated.
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ data &outds;
|
|||||||
if libref(lib) ne 0 then do;
|
if libref(lib) ne 0 then do;
|
||||||
msg=catx(' ','libref',lib,'is not assigned!');
|
msg=catx(' ','libref',lib,'is not assigned!');
|
||||||
%if &errds=0 %then %do;
|
%if &errds=0 %then %do;
|
||||||
putlog "%str(ERR)OR: " msg;
|
putlog 'ERR' +(-1) "OR: " msg;
|
||||||
%end;
|
%end;
|
||||||
output;
|
output;
|
||||||
return;
|
return;
|
||||||
@@ -102,7 +102,7 @@ data &outds;
|
|||||||
if exist(cats(lib,'.',ds)) ne 1 then do;
|
if exist(cats(lib,'.',ds)) ne 1 then do;
|
||||||
msg=catx(' ','libds',lib,'.',ds,'does not exist!');
|
msg=catx(' ','libds',lib,'.',ds,'does not exist!');
|
||||||
%if &errds=0 %then %do;
|
%if &errds=0 %then %do;
|
||||||
putlog "%str(ERR)OR: " msg;
|
putlog 'ERR' +(-1) "OR: " msg;
|
||||||
%end;
|
%end;
|
||||||
output;
|
output;
|
||||||
return;
|
return;
|
||||||
@@ -111,7 +111,7 @@ data &outds;
|
|||||||
if is_fmt=0 then do;
|
if is_fmt=0 then do;
|
||||||
msg=catx(' ','format',fmt,'on libds',lib,'.',ds,'.',var,'is not valid!');
|
msg=catx(' ','format',fmt,'on libds',lib,'.',ds,'.',var,'is not valid!');
|
||||||
%if &errds=0 %then %do;
|
%if &errds=0 %then %do;
|
||||||
putlog "%str(ERR)OR: " msg;
|
putlog 'ERR' +(-1) "OR: " msg;
|
||||||
%end;
|
%end;
|
||||||
output;
|
output;
|
||||||
return;
|
return;
|
||||||
@@ -123,7 +123,7 @@ data &outds;
|
|||||||
if dsid=0 then do;
|
if dsid=0 then do;
|
||||||
msg=catx(' ','libds',lib,'.',ds,' could not be opened!');
|
msg=catx(' ','libds',lib,'.',ds,' could not be opened!');
|
||||||
%if &errds=0 %then %do;
|
%if &errds=0 %then %do;
|
||||||
putlog "%str(ERR)OR: " msg;
|
putlog 'ERR' +(-1) "OR: " msg;
|
||||||
%end;
|
%end;
|
||||||
output;
|
output;
|
||||||
return;
|
return;
|
||||||
@@ -131,7 +131,7 @@ data &outds;
|
|||||||
if varnum(dsid,var)<1 then do;
|
if varnum(dsid,var)<1 then do;
|
||||||
msg=catx(' ','Variable',lib,'.',ds,'.',var,' was not found!');
|
msg=catx(' ','Variable',lib,'.',ds,'.',var,' was not found!');
|
||||||
%if &errds=0 %then %do;
|
%if &errds=0 %then %do;
|
||||||
putlog "%str(ERR)OR: " msg;
|
putlog 'ERR' +(-1) "OR: " msg;
|
||||||
%end;
|
%end;
|
||||||
output;
|
output;
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
results. If it does not exist, it will be created, with the following format:
|
results. If it does not exist, it will be created, with the following format:
|
||||||
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|User Provided description|PASS|Column &inds contained ALL columns|
|
|User Provided description|PASS|Dataset &inds contained ALL columns|
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|||||||
@@ -31,8 +31,8 @@
|
|||||||
|
|
||||||
|
|
||||||
@param [in] inds The input library.dataset to test for values
|
@param [in] inds The input library.dataset to test for values
|
||||||
@param [in] cols= The list of columns to check for
|
@param [in] cols= (0) The list of columns to check for
|
||||||
@param [in] desc= (Testing observations) The user provided test description
|
@param [in] desc= (0) The user provided test description
|
||||||
@param [in] test= (ALL) The test to apply. Valid values are:
|
@param [in] test= (ALL) The test to apply. Valid values are:
|
||||||
@li ALL - Test is a PASS if ALL columns exist in &inds
|
@li ALL - Test is a PASS if ALL columns exist in &inds
|
||||||
@li ANY - Test is a PASS if ANY of the columns exist in &inds
|
@li ANY - Test is a PASS if ANY of the columns exist in &inds
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
results. If it does not exist, it will be created, with the following format:
|
results. If it does not exist, it will be created, with the following format:
|
||||||
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|User Provided description|PASS|Column &inds contained ALL columns|
|
|User Provided description|PASS|Dataset &inds contained ALL columns|
|
||||||
|
|
||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
|
|||||||
@@ -36,12 +36,13 @@
|
|||||||
|
|
||||||
|
|
||||||
@param [in] indscol The input library.dataset.column to test for values
|
@param [in] indscol The input library.dataset.column to test for values
|
||||||
@param [in] checkvals= A library.dataset.column value containing a UNIQUE
|
@param [in] checkvals= (0) A library.dataset.column value containing a UNIQUE
|
||||||
list of values to be compared against the source (indscol).
|
list of values to be compared against the source (indscol).
|
||||||
@param [in] desc= (Testing observations) The user provided test description
|
@param [in] desc= (Testing observations) The user provided test description
|
||||||
@param [in] test= (ALLVALS) The test to apply. Valid values are:
|
@param [in] test= (ALLVALS) The test to apply. Valid values are:
|
||||||
@li ALLVALS - Test is a PASS if ALL values have a match in checkvals
|
@li ALLVALS - Test is a PASS if ALL values have a match in checkvals
|
||||||
@li ANYVAL - Test is a PASS if at least 1 value has a match in checkvals
|
@li ANYVAL - Test is a PASS if at least 1 value has a match in checkvals
|
||||||
|
@li NOVAL - Test is a PASS if there are NO matches in checkvals
|
||||||
@param [out] outds= (work.test_results) The output dataset to contain the
|
@param [out] outds= (work.test_results) The output dataset to contain the
|
||||||
results. If it does not exist, it will be created, with the following format:
|
results. If it does not exist, it will be created, with the following format:
|
||||||
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|
||||||
@@ -97,7 +98,7 @@
|
|||||||
|
|
||||||
%let test=%upcase(&test);
|
%let test=%upcase(&test);
|
||||||
|
|
||||||
%if &test ne ALLVALS and &test ne ANYVAL %then %do;
|
%if &test ne ALLVALS and &test ne ANYVAL and &test ne NOVAL %then %do;
|
||||||
%mp_abort(
|
%mp_abort(
|
||||||
mac=&sysmacroname,
|
mac=&sysmacroname,
|
||||||
msg=%str(Invalid test - &test)
|
msg=%str(Invalid test - &test)
|
||||||
@@ -108,12 +109,12 @@
|
|||||||
%let result=-1;
|
%let result=-1;
|
||||||
%let orig=-1;
|
%let orig=-1;
|
||||||
proc sql noprint;
|
proc sql noprint;
|
||||||
select count(*) into: result
|
select count(*) into: result trimmed
|
||||||
from &lib..&ds
|
from &lib..&ds
|
||||||
where &col not in (
|
where &col not in (
|
||||||
select &ccol from &clib..&cds
|
select &ccol from &clib..&cds
|
||||||
);
|
);
|
||||||
select count(*) into: orig from &lib..&ds;
|
select count(*) into: orig trimmed from &lib..&ds;
|
||||||
quit;
|
quit;
|
||||||
|
|
||||||
%local notfound tmp1 tmp2;
|
%local notfound tmp1 tmp2;
|
||||||
@@ -145,7 +146,7 @@
|
|||||||
length test_description $256 test_result $4 test_comments $256;
|
length test_description $256 test_result $4 test_comments $256;
|
||||||
test_description=symget('desc');
|
test_description=symget('desc');
|
||||||
test_result='FAIL';
|
test_result='FAIL';
|
||||||
test_comments="&sysmacroname: &lib..&ds..&col has &result values "
|
test_comments="&sysmacroname: &lib..&ds..&col has &result/&orig values "
|
||||||
!!"not in &clib..&cds..&ccol.. First 10 vals:"!!symget('notfound');
|
!!"not in &clib..&cds..&ccol.. First 10 vals:"!!symget('notfound');
|
||||||
%if &test=ANYVAL %then %do;
|
%if &test=ANYVAL %then %do;
|
||||||
if &result < &orig then test_result='PASS';
|
if &result < &orig then test_result='PASS';
|
||||||
@@ -153,6 +154,9 @@
|
|||||||
%else %if &test=ALLVALS %then %do;
|
%else %if &test=ALLVALS %then %do;
|
||||||
if &result=0 then test_result='PASS';
|
if &result=0 then test_result='PASS';
|
||||||
%end;
|
%end;
|
||||||
|
%else %if &test=NOVAL %then %do;
|
||||||
|
if &result=&orig then test_result='PASS';
|
||||||
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
test_comments="&sysmacroname: Unsatisfied test condition - &test";
|
test_comments="&sysmacroname: Unsatisfied test condition - &test";
|
||||||
%end;
|
%end;
|
||||||
|
|||||||
@@ -28,8 +28,8 @@
|
|||||||
put _infile_;
|
put _infile_;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
@param [in] inref= Fileref of the input file (should exist)
|
@param [in] inref= (0) Fileref of the input file (should exist)
|
||||||
@param [out] outref= Output fileref. If it does not exist, it is created.
|
@param [out] outref= (0) Output fileref. If it does not exist, it is created.
|
||||||
@param [in] action= (ENCODE) The action to take. Valid values:
|
@param [in] action= (ENCODE) The action to take. Valid values:
|
||||||
@li ENCODE - Convert the file to base64 format
|
@li ENCODE - Convert the file to base64 format
|
||||||
@li DECODE - Decode the file from base64 format
|
@li DECODE - Decode the file from base64 format
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
@details Reads in a file byte by byte and writes it back out. Is an
|
@details Reads in a file byte by byte and writes it back out. Is an
|
||||||
os-independent method to copy files. In case of naming collision, the
|
os-independent method to copy files. In case of naming collision, the
|
||||||
default filerefs can be modified.
|
default filerefs can be modified.
|
||||||
Based on:
|
Note that if you have a new enough version of SAS, and you don't need features
|
||||||
https://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file
|
such as APPEND, you may be better of using the fcopy() function instead.
|
||||||
|
|
||||||
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
|
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
|
||||||
|
|
||||||
@@ -22,15 +22,17 @@
|
|||||||
%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 [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 [out] outloc= () quoted "path/and/filename.ext" of the file to create
|
||||||
@param [in] inref (____in) If provided, this fileref will take precedence over
|
@param [in] inref= (____in) If provided, this fileref takes precedence over
|
||||||
the `inloc` parameter
|
the `inloc` parameter
|
||||||
@param [out] outref (____in) If provided, this fileref will take precedence
|
@param [out] outref= (____in) If provided, this fileref takes precedence
|
||||||
over the `outloc` parameter. It must already exist!
|
over the `outloc` parameter. It must already exist!
|
||||||
@param [in] mode (CREATE) Valid values:
|
@param [in] mode= (CREATE) Valid values:
|
||||||
@li CREATE - Create the file (even if it already exists)
|
@li CREATE - Create the file (even if it already exists)
|
||||||
@li APPEND - Append to the file (don't overwrite)
|
@li APPEND - Append to the file (don't overwrite)
|
||||||
|
@param [in] iftrue= (1=1)
|
||||||
|
Supply a condition for which the macro should be executed
|
||||||
|
|
||||||
@returns nothing
|
@returns nothing
|
||||||
|
|
||||||
@@ -44,15 +46,14 @@
|
|||||||
,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
|
,mode=CREATE
|
||||||
|
,iftrue=%str(1=1)
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%local mod outmode;
|
%local mod;
|
||||||
%if &mode=APPEND %then %do;
|
|
||||||
%let mod=mod;
|
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||||
%let outmode='a';
|
|
||||||
%end;
|
%if &mode=APPEND %then %let mod=mod;
|
||||||
%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 ;
|
||||||
@@ -63,22 +64,17 @@
|
|||||||
|
|
||||||
/* copy the file byte-for-byte */
|
/* copy the file byte-for-byte */
|
||||||
data _null_;
|
data _null_;
|
||||||
length filein 8 fileid 8;
|
infile &inref lrecl=1 recfm=n;
|
||||||
filein = fopen("&inref",'I',1,'B');
|
file &outref &mod recfm=n;
|
||||||
fileid = fopen("&outref",&outmode,1,'B');
|
input sourcechar $char1. @@;
|
||||||
rec = '20'x;
|
format sourcechar hex2.;
|
||||||
do while(fread(filein)=0);
|
put sourcechar char1. @@;
|
||||||
rc = fget(filein,rec,1);
|
|
||||||
rc = fput(fileid, rec);
|
|
||||||
rc =fwrite(fileid);
|
|
||||||
end;
|
|
||||||
rc = fclose(filein);
|
|
||||||
rc = fclose(fileid);
|
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%if &inref = ____in %then %do;
|
%if &inref = ____in %then %do;
|
||||||
filename &inref clear;
|
filename &inref clear;
|
||||||
%end;
|
%end;
|
||||||
%if &outref=____out %then %do;
|
%if &outref=____out %then %do;
|
||||||
filename &outref clear;
|
filename &outref clear;
|
||||||
%end;
|
%end;
|
||||||
%mend mp_binarycopy;
|
%mend mp_binarycopy;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
For more examples, see mp_chop.test.sas
|
For more examples, see mp_chop.test.sas
|
||||||
|
|
||||||
@param [in] infile The QUOTED path to the file on which to perform the chop
|
@param [in] infile The QUOTED path to the file on which to perform the chop
|
||||||
@param [in] matchvar= Macro variable NAME containing the string to split by
|
@param [in] matchvar= () Macro variable NAME containing the string to split by
|
||||||
@param [in] matchpoint= (START) Valid values:
|
@param [in] matchpoint= (START) Valid values:
|
||||||
@li START - chop at the beginning of the string in `matchvar`.
|
@li START - chop at the beginning of the string in `matchvar`.
|
||||||
@li END - chop at the end of the string in `matchvar`.
|
@li END - chop at the end of the string in `matchvar`.
|
||||||
@@ -54,7 +54,8 @@
|
|||||||
@li FIRST - keep the section of the file before the chop
|
@li FIRST - keep the section of the file before the chop
|
||||||
@li LAST - keep the section of the file after the chop
|
@li LAST - keep the section of the file after the chop
|
||||||
@param [in] mdebug= (0) Set to 1 to provide macro debugging
|
@param [in] mdebug= (0) Set to 1 to provide macro debugging
|
||||||
@param outfile= (0) Optional QUOTED path to the adjusted output file (avoids
|
@param [out] outfile= (0)
|
||||||
|
Optional QUOTED path to the adjusted output file (avoids
|
||||||
overwriting the first file).
|
overwriting the first file).
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@@ -185,7 +186,7 @@ run;
|
|||||||
infile &outfile lrecl=32767;
|
infile &outfile lrecl=32767;
|
||||||
input;
|
input;
|
||||||
list;
|
list;
|
||||||
if _n_>50 then stop;
|
if _n_>200 then stop;
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
/* END */
|
/* END */
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
@file mp_cleancsv.sas
|
@file
|
||||||
@brief Fixes embedded cr / lf / crlf in CSV
|
@brief Fixes embedded cr / lf / crlf in CSV
|
||||||
@details CSVs will sometimes contain lf or crlf within quotes (eg when
|
@details CSVs will sometimes contain lf or crlf within quotes (eg when
|
||||||
saved by excel). When the termstr is ALSO lf or crlf that can be tricky
|
saved by excel). When the termstr is ALSO lf or crlf that can be tricky
|
||||||
@@ -7,14 +7,17 @@
|
|||||||
This macro converts any csv to follow the convention of a windows excel file,
|
This macro converts any csv to follow the convention of a windows excel file,
|
||||||
applying CRLF line endings and converting embedded cr and crlf to lf.
|
applying CRLF line endings and converting embedded cr and crlf to lf.
|
||||||
|
|
||||||
usage:
|
Usage:
|
||||||
|
|
||||||
fileref mycsv "/path/your/csv";
|
fileref mycsv "/path/your/csv";
|
||||||
%mp_cleancsv(in=mycsv,out=/path/new.csv)
|
%mp_cleancsv(in=mycsv,out=/path/new.csv)
|
||||||
|
|
||||||
@param in= provide path or fileref to input csv
|
@param [in] in= (NOTPROVIDED)
|
||||||
@param out= output path or fileref to output csv
|
Provide path or fileref to input csv. If a period is
|
||||||
@param qchar= quote char - hex code 22 is the double quote.
|
found, it is assumed to be a file.
|
||||||
|
@param [in] out= (NOTPROVIDED) Output path or fileref to output csv.
|
||||||
|
If a period is found, it is assumed to be a file.
|
||||||
|
@param [in] qchar= ('22'x) Quote char - hex code 22 is the double quote.
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@@ -56,9 +59,14 @@
|
|||||||
else do;
|
else do;
|
||||||
/* outside a quote, change cr and lf to crlf */
|
/* outside a quote, change cr and lf to crlf */
|
||||||
if inchar='0D'x then do;
|
if inchar='0D'x then do;
|
||||||
|
crblank:
|
||||||
put '0D0A'x;
|
put '0D0A'x;
|
||||||
input inchar $char1.;
|
input inchar $char1.;
|
||||||
if inchar ne '0A'x then do;
|
if inchar='0D'x then do;
|
||||||
|
/* multiple CR indicates CR formatted file with blank lines */
|
||||||
|
goto crblank;
|
||||||
|
end;
|
||||||
|
else if inchar ne '0A'x then do;
|
||||||
put inchar $char1.;
|
put inchar $char1.;
|
||||||
if inchar=qchar then isq = mod(isq+1,2);
|
if inchar=qchar then isq = mod(isq+1,2);
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mddl_sas_cntlout.sas
|
@li mddl_sas_cntlout.sas
|
||||||
@li mf_getuniquename.sas
|
@li mf_getuniquename.sas
|
||||||
|
@li mp_aligndecimal.sas
|
||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
@li mf_getvarformat.sas
|
@li mf_getvarformat.sas
|
||||||
@@ -57,25 +58,33 @@
|
|||||||
%end;
|
%end;
|
||||||
|
|
||||||
proc format lib=&libcat cntlout=&cntlds;
|
proc format lib=&libcat cntlout=&cntlds;
|
||||||
%if "&fmtlist" ne "0" %then %do;
|
%if "&fmtlist" ne "0" and "&fmtlist" ne "" %then %do;
|
||||||
select
|
select
|
||||||
%do i=1 %to %sysfunc(countw(&fmtlist));
|
%do i=1 %to %sysfunc(countw(&fmtlist,%str( )));
|
||||||
%scan(&fmtlist,&i,%str( ))
|
%scan(&fmtlist,&i,%str( ))
|
||||||
%end;
|
%end;
|
||||||
;
|
;
|
||||||
%end;
|
%end;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
data &cntlout;
|
data &cntlout/nonote2err;
|
||||||
if 0 then set &ddlds;
|
if 0 then set &ddlds;
|
||||||
set &cntlds;
|
set &cntlds;
|
||||||
if type="N" then do;
|
by type fmtname notsorted;
|
||||||
start=cats(start);
|
|
||||||
end=cats(end);
|
/* align the numeric values to avoid overlapping ranges */
|
||||||
|
if type in ("I","N") then do;
|
||||||
|
%mp_aligndecimal(start,width=16)
|
||||||
|
%mp_aligndecimal(end,width=16)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
/* create row marker. Data cannot be sorted without it! */
|
||||||
|
if first.fmtname then fmtrow=0;
|
||||||
|
fmtrow+1;
|
||||||
|
|
||||||
run;
|
run;
|
||||||
proc sort;
|
proc sort;
|
||||||
by fmtname start;
|
by type fmtname fmtrow;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
proc sql;
|
proc sql;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
@param [in] source Unquoted path to the folder to copy from.
|
@param [in] source Unquoted path to the folder to copy from.
|
||||||
@param [out] target Unquoted path to the folder to copy to.
|
@param [out] target Unquoted path to the folder to copy to.
|
||||||
@param [in] copymax=(MAX) Set to a positive integer to indicate the level of
|
@param [in] copymax= (MAX) Set to a positive integer to indicate the level of
|
||||||
subdirectory copy recursion - eg 3, to go `./3/levels/deep`. For unlimited
|
subdirectory copy recursion - eg 3, to go `./3/levels/deep`. For unlimited
|
||||||
recursion, set to MAX.
|
recursion, set to MAX.
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
rc2=filename(fref2,filepath2,'disk','recfm=n');
|
rc2=filename(fref2,filepath2,'disk','recfm=n');
|
||||||
if fcopy(fref1,fref2) ne 0 then do;
|
if fcopy(fref1,fref2) ne 0 then do;
|
||||||
msg=sysmsg();
|
msg=sysmsg();
|
||||||
putlog "%str(ERR)OR: Unable to copy " filepath " to " filepath2;
|
putlog 'ERR' +(-1) "OR: Unable to copy " filepath " to " filepath2;
|
||||||
putlog msg=;
|
putlog msg=;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -18,11 +18,14 @@
|
|||||||
%mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES)
|
%mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES)
|
||||||
%mp_createconstraints(inds=work.constraints,outds=created,execute=YES)
|
%mp_createconstraints(inds=work.constraints,outds=created,execute=YES)
|
||||||
|
|
||||||
@param inds= The input table containing the constraint info
|
@param [in] inds= (work.mp_getconstraints) The input table containing the
|
||||||
@param outds= a table containing the create statements (create_statement column)
|
constraint info
|
||||||
@param execute= `YES|NO` - default is NO. To actually create, use YES.
|
@param [out] outds= (work.mp_createconstraints) A table containing the create
|
||||||
|
statements (create_statement column)
|
||||||
|
@param [in] execute= (NO) To actually create, use YES.
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> Related Files </h4>
|
||||||
|
@li mp_getconstraints.sas
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@@ -30,7 +33,7 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mp_createconstraints(inds=mp_getconstraints
|
%macro mp_createconstraints(inds=mp_getconstraints
|
||||||
,outds=mp_createconstraints
|
,outds=work.mp_createconstraints
|
||||||
,execute=NO
|
,execute=NO
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
@@ -64,4 +67,4 @@ data &outds;
|
|||||||
output;
|
output;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%mend mp_createconstraints;
|
%mend mp_createconstraints;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
@file mp_createwebservice.sas
|
@file mp_createwebservice.sas
|
||||||
@brief Create a web service in SAS 9, Viya or SASjs Server
|
@brief Create a web service in SAS 9, Viya or SASjs Server (legacy)
|
||||||
@details This is actually a wrapper for mx_createwebservice.sas, remaining
|
@details This is actually a wrapper for mx_createwebservice.sas, remaining
|
||||||
for legacy purposes. For new apps, use mx_createwebservice.sas.
|
for legacy purposes. For new apps, use mx_createwebservice.sas.
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,12 @@
|
|||||||
%mp_csv2ds(inref=mycsv,outds=myds,baseds=sashelp.class)
|
%mp_csv2ds(inref=mycsv,outds=myds,baseds=sashelp.class)
|
||||||
|
|
||||||
|
|
||||||
@param inref= fileref to the CSV
|
@param [in] inref= (0) Fileref to the CSV
|
||||||
@param outds= output ds (lib.ds format)
|
@param [out] outds= (0) Output ds (lib.ds format)
|
||||||
@param view= Set to YES or NO to determine whether the output should be
|
@param [in] view= (NO) Set to YES or NO to determine whether the output
|
||||||
a view or not. Default is NO (not a view).
|
should be a view or not. Default is NO (not a view).
|
||||||
@param baseds= Template dataset on which to create the input statement.
|
@param [in] baseds= (0)
|
||||||
|
Template dataset on which to create the input statement.
|
||||||
Is used to determine types, lengths, and any informats.
|
Is used to determine types, lengths, and any informats.
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
|
|||||||
@@ -17,9 +17,11 @@
|
|||||||
%mp_getconstraints(lib=work,ds=example,outds=work.constraints)
|
%mp_getconstraints(lib=work,ds=example,outds=work.constraints)
|
||||||
%mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES)
|
%mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES)
|
||||||
|
|
||||||
@param inds= The input table containing the constraint info
|
@param [in] inds= (mp_getconstraints)
|
||||||
@param outds= a table containing the drop statements (drop_statement column)
|
The input table containing constraint info
|
||||||
@param execute= `YES|NO` - default is NO. To actually drop, use YES.
|
@param [out] outds= (mp_deleteconstraints)
|
||||||
|
Table containing the drop statements (drop_statement column)
|
||||||
|
@param [in] execute= (NO) `YES|NO` - default is NO. To actually drop, use YES.
|
||||||
|
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
%mp_deletefolder(&rootdir)
|
%mp_deletefolder(&rootdir)
|
||||||
|
|
||||||
@param path Unquoted path to the folder to delete.
|
@param [in] folder Unquoted path to the folder to delete.
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_getuniquename.sas
|
@li mf_getuniquename.sas
|
||||||
|
|||||||
52
base/mp_dictionary.sas
Normal file
52
base/mp_dictionary.sas
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
@file mp_dictionary.sas
|
||||||
|
@brief Creates a portal (libref) into the SQL Dictionary Views
|
||||||
|
@details Provide a libref and the macro will create a series of views against
|
||||||
|
each view in the special PROC SQL dictionary libref.
|
||||||
|
|
||||||
|
This is useful if you would like to visualise (navigate) the views in a SAS
|
||||||
|
client such as Base SAS, Enterprise Guide, or Studio (or [Data Controller](
|
||||||
|
https://datacontroller.io)).
|
||||||
|
|
||||||
|
It works by extracting the dictionary.dictionaries view into
|
||||||
|
YOURLIB.dictionaries, then uses that to create a YOURLIB.{viewName} for every
|
||||||
|
other dictionary.view, eg:
|
||||||
|
|
||||||
|
proc sql;
|
||||||
|
create view YOURLIB.columns as select * from dictionary.columns;
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
libname demo "/lib/directory";
|
||||||
|
%mp_dictionary(lib=demo)
|
||||||
|
|
||||||
|
Or, to just create them in WORK:
|
||||||
|
|
||||||
|
%mp_dictionary()
|
||||||
|
|
||||||
|
If you'd just like to browse the dictionary data model, you can also check
|
||||||
|
out [this article](https://rawsas.com/dictionary-of-dictionaries/).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
@param [in] lib= (WORK) The libref in which to create the views
|
||||||
|
|
||||||
|
<h4> Related Files </h4>
|
||||||
|
@li mp_dictionary.test.sas
|
||||||
|
|
||||||
|
@version 9.2
|
||||||
|
@author Allan Bowe
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_dictionary(lib=WORK)/*/STORE SOURCE*/;
|
||||||
|
%local list i mem;
|
||||||
|
proc sql noprint;
|
||||||
|
create view &lib..dictionaries as select * from dictionary.dictionaries;
|
||||||
|
select distinct memname into: list separated by ' ' from &lib..dictionaries;
|
||||||
|
%do i=1 %to %sysfunc(countw(&list,%str( )));
|
||||||
|
%let mem=%scan(&list,&i,%str( ));
|
||||||
|
create view &lib..&mem as select * from dictionary.&mem;
|
||||||
|
%end;
|
||||||
|
quit;
|
||||||
|
%mend mp_dictionary;
|
||||||
@@ -27,6 +27,9 @@
|
|||||||
@param [in] maxdepth= (0) Set to a positive integer to indicate the level of
|
@param [in] maxdepth= (0) Set to a positive integer to indicate the level of
|
||||||
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
|
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
|
||||||
recursion, set to MAX.
|
recursion, set to MAX.
|
||||||
|
@param [in] showparent= (NO) By default, the initial parent directory is not
|
||||||
|
part of the results. Set to YES to include it. For this record only,
|
||||||
|
directory=filepath.
|
||||||
@param [out] outds= (work.mp_dirlist) The output dataset to create
|
@param [out] outds= (work.mp_dirlist) The output dataset to create
|
||||||
@param [out] getattrs= (NO) If getattrs=YES then the doptname / foptname
|
@param [out] getattrs= (NO) If getattrs=YES then the doptname / foptname
|
||||||
functions are used to scan all properties - any characters that are not
|
functions are used to scan all properties - any characters that are not
|
||||||
@@ -63,6 +66,7 @@
|
|||||||
, fref=0
|
, fref=0
|
||||||
, outds=work.mp_dirlist
|
, outds=work.mp_dirlist
|
||||||
, getattrs=NO
|
, getattrs=NO
|
||||||
|
, showparent=NO
|
||||||
, maxdepth=0
|
, maxdepth=0
|
||||||
, level=0 /* The level of recursion to perform. For internal use only. */
|
, level=0 /* The level of recursion to perform. For internal use only. */
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
@@ -97,8 +101,7 @@ data &out_ds(compress=no
|
|||||||
if did=0 then do;
|
if did=0 then do;
|
||||||
putlog "NOTE: This directory is empty, or does not exist - &path";
|
putlog "NOTE: This directory is empty, or does not exist - &path";
|
||||||
msg=sysmsg();
|
msg=sysmsg();
|
||||||
put msg;
|
put (_all_)(=);
|
||||||
put _all_;
|
|
||||||
stop;
|
stop;
|
||||||
end;
|
end;
|
||||||
/* attribute is OS-dependent - could be "Directory" or "Directory Name" */
|
/* attribute is OS-dependent - could be "Directory" or "Directory Name" */
|
||||||
@@ -145,6 +148,15 @@ data &out_ds(compress=no
|
|||||||
output;
|
output;
|
||||||
end;
|
end;
|
||||||
rc = dclose(did);
|
rc = dclose(did);
|
||||||
|
%if &showparent=YES and &level=0 %then %do;
|
||||||
|
filepath=directory;
|
||||||
|
file_or_folder='folder';
|
||||||
|
ext='';
|
||||||
|
filename=scan(directory,-1,'/\');
|
||||||
|
msg='';
|
||||||
|
level=&level;
|
||||||
|
output;
|
||||||
|
%end;
|
||||||
stop;
|
stop;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
@@ -232,6 +244,9 @@ run;
|
|||||||
data _null_;
|
data _null_;
|
||||||
set &out_ds;
|
set &out_ds;
|
||||||
where file_or_folder='folder';
|
where file_or_folder='folder';
|
||||||
|
%if &showparent=YES and &level=0 %then %do;
|
||||||
|
if filepath ne directory;
|
||||||
|
%end;
|
||||||
length code $10000;
|
length code $10000;
|
||||||
code=cats('%nrstr(%mp_dirlist(path=',filepath,",outds=&outds"
|
code=cats('%nrstr(%mp_dirlist(path=',filepath,",outds=&outds"
|
||||||
,",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");
|
,",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
|
|
||||||
%mp_distinctfmtvalues(libds=sashelp.class,var=age,outvar=age,outds=test)
|
%mp_distinctfmtvalues(libds=sashelp.class,var=age,outvar=age,outds=test)
|
||||||
|
|
||||||
@param libds input dataset
|
@param [in] libds= () input dataset
|
||||||
@param var variable to get distinct values for
|
@param [in] var= (0) variable to get distinct values for
|
||||||
@param outvar variable to create. Default: `formatted_value`
|
@param [out] outvar= (formatteed_value) variable to create.
|
||||||
@param outds dataset to create. Default: work.mp_distinctfmtvalues
|
@param [out] outds= (work.mp_distinctfmtvalues) dataset to create.
|
||||||
@param varlen length of variable to create (default 200)
|
@param [in] varlen= (2000) length of variable to create
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
@cond
|
@cond
|
||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mp_ds2cards(base_ds=, tgt_ds=
|
%macro mp_ds2cards(base_ds, tgt_ds=
|
||||||
,cards_file="%sysfunc(pathname(work))/cardgen.sas"
|
,cards_file="%sysfunc(pathname(work))/cardgen.sas"
|
||||||
,maxobs=max
|
,maxobs=max
|
||||||
,random_sample=NO
|
,random_sample=NO
|
||||||
@@ -254,6 +254,7 @@ data _null_;
|
|||||||
;
|
;
|
||||||
%end;
|
%end;
|
||||||
put ";";
|
put ";";
|
||||||
|
put 'missing a b c d e f g h i j k l m n o p q r s t u v w x y z _;';
|
||||||
put "datalines4;";
|
put "datalines4;";
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
/**
|
/**
|
||||||
@file
|
@file
|
||||||
@brief A wrapper for mp_getddl.sas
|
@brief Fetches DDL for a specific table
|
||||||
@details In the next release, this will be the main version.
|
@details Uses mp_getddl under the hood
|
||||||
|
|
||||||
|
@param [in] libds library.dataset to create ddl for
|
||||||
|
@param [in] fref= (getddl) the fileref to which to _append_ the DDL. If it
|
||||||
|
does not exist, it will be created.
|
||||||
|
@param [in] flavour= (SAS) The type of DDL to create. Options:
|
||||||
|
@li SAS
|
||||||
|
@li TSQL
|
||||||
|
|
||||||
|
@param [in]showlog= (NO) Set to YES to show the DDL in the log
|
||||||
|
@param [in] schema= () Choose a preferred schema name (default is to use
|
||||||
|
actual schema, else libref)
|
||||||
|
@param [in] applydttm= (NO) For non SAS DDL, choose if columns are created with
|
||||||
|
native datetime2 format or regular decimal type
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mp_getddl.sas
|
@li mp_getddl.sas
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_existds.sas
|
@li mf_existds.sas
|
||||||
|
|
||||||
<h4> Related Macros <h4>
|
<h4> Related Macros </h4>
|
||||||
@li mp_jsonout.sas
|
@li mp_jsonout.sas
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
|
|||||||
@@ -92,12 +92,13 @@ data _null_;
|
|||||||
run;
|
run;
|
||||||
|
|
||||||
%if %upcase(&showlog)=YES %then %do;
|
%if %upcase(&showlog)=YES %then %do;
|
||||||
options ps=max;
|
options ps=max lrecl=max;
|
||||||
data _null_;
|
data _null_;
|
||||||
infile &outref;
|
infile &outref;
|
||||||
|
if _n_=1 then putlog "# &libds" /;
|
||||||
input;
|
input;
|
||||||
putlog _infile_;
|
putlog _infile_;
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
%mend mp_ds2md;
|
%mend mp_ds2md;
|
||||||
|
|||||||
110
base/mp_dsmeta.sas
Normal file
110
base/mp_dsmeta.sas
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Export dataset metadata to a single output table
|
||||||
|
@details Exports the dataset attributes and enginehost information, then
|
||||||
|
converts the datasets into a single output table in the following format:
|
||||||
|
|
||||||
|
|ODS_TABLE:$10.|NAME:$100.|VALUE:$1000.|
|
||||||
|
|---|---|---|
|
||||||
|
|`ATTRIBUTES `|`Data Set Name `|`SASHELP.CLASS `|
|
||||||
|
|`ATTRIBUTES `|`Observations `|`19 `|
|
||||||
|
|`ATTRIBUTES `|`Member Type `|`DATA `|
|
||||||
|
|`ATTRIBUTES `|`Variables `|`5 `|
|
||||||
|
|`ATTRIBUTES `|`Engine `|`V9 `|
|
||||||
|
|`ATTRIBUTES `|`Indexes `|`0 `|
|
||||||
|
|`ATTRIBUTES `|`Created `|`06/08/2020 00:59:14 `|
|
||||||
|
|`ATTRIBUTES `|`Observation Length `|`40 `|
|
||||||
|
|`ATTRIBUTES `|`Last Modified `|`06/08/2020 00:59:14 `|
|
||||||
|
|`ATTRIBUTES `|`Deleted Observations `|`0 `|
|
||||||
|
|`ATTRIBUTES `|`Protection `|`. `|
|
||||||
|
|`ATTRIBUTES `|`Compressed `|`NO `|
|
||||||
|
|`ATTRIBUTES `|`Data Set Type `|`. `|
|
||||||
|
|`ATTRIBUTES `|`Sorted `|`NO `|
|
||||||
|
|`ATTRIBUTES `|`Label `|`Student Data `|
|
||||||
|
|`ATTRIBUTES `|`Data Representation `|`SOLARIS_X86_64, LINUX_X86_64, ALPHA_TRU64, LINUX_IA64 `|
|
||||||
|
|`ATTRIBUTES `|`Encoding `|`us-ascii ASCII (ANSI) `|
|
||||||
|
|`ENGINEHOST `|`Data Set Page Size `|`65536 `|
|
||||||
|
|`ENGINEHOST `|`Number of Data Set Pages `|`1 `|
|
||||||
|
|`ENGINEHOST `|`First Data Page `|`1 `|
|
||||||
|
|`ENGINEHOST `|`Max Obs per Page `|`1632 `|
|
||||||
|
|`ENGINEHOST `|`Obs in First Data Page `|`19 `|
|
||||||
|
|`ENGINEHOST `|`Number of Data Set Repairs `|`0 `|
|
||||||
|
|`ENGINEHOST `|`Filename `|`/opt/sas/sas9/SASHome/SASFoundation/9.4/sashelp/class.sas7bdat `|
|
||||||
|
|`ENGINEHOST `|`Release Created `|`9.0401M7 `|
|
||||||
|
|`ENGINEHOST `|`Host Created `|`Linux `|
|
||||||
|
|`ENGINEHOST `|`Inode Number `|`28314616 `|
|
||||||
|
|`ENGINEHOST `|`Access Permission `|`rw-r--r-- `|
|
||||||
|
|`ENGINEHOST `|`Owner Name `|`sas `|
|
||||||
|
|`ENGINEHOST `|`File Size `|`128KB `|
|
||||||
|
|`ENGINEHOST `|`File Size (bytes) `|`131072 `|
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
%mp_dsmeta(sashelp.class,outds=work.mymeta)
|
||||||
|
proc print data=work.mymeta;
|
||||||
|
run;
|
||||||
|
|
||||||
|
For more details on creating datasets from PROC CONTENTS check out this
|
||||||
|
excellent [paper](
|
||||||
|
https://support.sas.com/resources/papers/proceedings14/1549-2014.pdf) by
|
||||||
|
[Louise Hadden](https://www.linkedin.com/in/louisehadden/).
|
||||||
|
|
||||||
|
@param [in] libds The library.dataset to export the metadata for
|
||||||
|
@param [out] outds= (work.dsmeta) The output table to contain the metadata
|
||||||
|
|
||||||
|
<h4> Related Files </h4>
|
||||||
|
@li mp_dsmeta.test.sas
|
||||||
|
@li mp_getcols.sas
|
||||||
|
@li mp_getdbml.sas
|
||||||
|
@li mp_getddl.sas
|
||||||
|
@li mp_getformats.sas
|
||||||
|
@li mp_getpk.sas
|
||||||
|
@li mp_guesspk.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_dsmeta(libds,outds=work.dsmeta);
|
||||||
|
|
||||||
|
%local ds1 ds2;
|
||||||
|
data;run; %let ds1=&syslast;
|
||||||
|
data;run; %let ds2=&syslast;
|
||||||
|
|
||||||
|
/* setup the ODS capture */
|
||||||
|
ods output attributes=&ds1 enginehost=&ds2;
|
||||||
|
|
||||||
|
/* export the metadata */
|
||||||
|
proc contents data=&libds;
|
||||||
|
run;
|
||||||
|
|
||||||
|
/* load it into a single table */
|
||||||
|
data &outds (keep=ods_table name value);
|
||||||
|
length ods_table $10 name label2 label1 label $100
|
||||||
|
value cvalue cvalue1 cvalue2 $1000
|
||||||
|
nvalue nvalue1 nvalue2 8;
|
||||||
|
if _n_=1 then call missing (of _all_);
|
||||||
|
* putlog (_all_)(=);
|
||||||
|
set &ds1 (in=atrs) &ds2 (in=eng);
|
||||||
|
if atrs then do;
|
||||||
|
ods_table='ATTRIBUTES';
|
||||||
|
name=coalescec(label1,label);
|
||||||
|
value=coalescec(cvalue1,cvalue,put(coalesce(nvalue1,nvalue),best.));
|
||||||
|
output;
|
||||||
|
if label2 ne '' then do;
|
||||||
|
name=label2;
|
||||||
|
value=coalescec(cvalue2,put(nvalue2,best.));
|
||||||
|
output;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
else if eng then do;
|
||||||
|
ods_table='ENGINEHOST';
|
||||||
|
name=coalescec(label1,label);
|
||||||
|
value=coalescec(cvalue1,cvalue,put(coalesce(nvalue1,nvalue),best.));
|
||||||
|
output;
|
||||||
|
end;
|
||||||
|
run;
|
||||||
|
|
||||||
|
proc sql;
|
||||||
|
drop table &ds1, &ds2;
|
||||||
|
|
||||||
|
%mend mp_dsmeta;
|
||||||
|
|
||||||
@@ -37,10 +37,12 @@
|
|||||||
@param [in] targetds= The target dataset against which to verify VARIABLE_NM.
|
@param [in] targetds= The target dataset against which to verify VARIABLE_NM.
|
||||||
This must be available (ie, the library must be assigned).
|
This must be available (ie, the library must be assigned).
|
||||||
@param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions
|
@param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions
|
||||||
@param [out] outds= The output table, which is a copy of the &inds. table
|
@param [out] outds= (work.badrecords) The output table, which is a copy of the
|
||||||
plus a REASON_CD column, containing only bad records. If bad records found,
|
&inds. table plus a REASON_CD column, containing only bad records.
|
||||||
the SYSCC value will be set to 1008 (general data problem). Downstream
|
If bad records are found, the SYSCC value will be set to 1008
|
||||||
processes should check this table (and return code) before continuing.
|
(a general data problem).
|
||||||
|
Downstream processes should check this table (and return code) before
|
||||||
|
continuing.
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mp_abort.sas
|
@li mp_abort.sas
|
||||||
@@ -92,7 +94,8 @@ data &outds;
|
|||||||
/*length GROUP_LOGIC SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32
|
/*length GROUP_LOGIC SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32
|
||||||
OPERATOR_NM $10 RAW_VALUE $4000;*/
|
OPERATOR_NM $10 RAW_VALUE $4000;*/
|
||||||
set &inds;
|
set &inds;
|
||||||
length reason_cd $4032 vtype $1 vnum dsid 8;
|
length reason_cd $4032 vtype $1 vnum dsid 8 tmp $4000;
|
||||||
|
drop tmp;
|
||||||
|
|
||||||
/* quick check to ensure column exists */
|
/* quick check to ensure column exists */
|
||||||
if upcase(VARIABLE_NM) not in
|
if upcase(VARIABLE_NM) not in
|
||||||
@@ -168,18 +171,32 @@ data &outds;
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
/* special logic */
|
/* special logic */
|
||||||
if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ','');
|
if OPERATOR_NM in ('IN','NOT IN','BETWEEN') then do;
|
||||||
else if OPERATOR_NM in ('IN','NOT IN') then do;
|
if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ',',');
|
||||||
if substr(raw_value,1,1) ne '('
|
else do;
|
||||||
or substr(cats(reverse(raw_value)),1,1) ne ')'
|
if substr(raw_value,1,1) ne '('
|
||||||
then do;
|
or substr(cats(reverse(raw_value)),1,1) ne ')'
|
||||||
REASON_CD='Missing start/end bracket in RAW_VALUE';
|
then do;
|
||||||
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
|
REASON_CD='Missing start/end bracket in RAW_VALUE';
|
||||||
call symputx('reason_cd',reason_cd,'l');
|
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
|
||||||
call symputx('nobs',_n_,'l');
|
call symputx('reason_cd',reason_cd,'l');
|
||||||
output;
|
call symputx('nobs',_n_,'l');
|
||||||
|
output;
|
||||||
|
end;
|
||||||
|
else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));
|
||||||
|
end;
|
||||||
|
/* we now have a comma seperated list of values */
|
||||||
|
if vtype='N' then do i=1 to countc(raw_value1, ',')+1;
|
||||||
|
tmp=scan(raw_value1,i,',');
|
||||||
|
if cats(tmp) ne '.' and input(tmp, ?? 8.) eq . then do;
|
||||||
|
REASON_CD='Non Numeric value provided';
|
||||||
|
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
|
||||||
|
call symputx('reason_cd',reason_cd,'l');
|
||||||
|
call symputx('nobs',_n_,'l');
|
||||||
|
output;
|
||||||
|
end;
|
||||||
|
return;
|
||||||
end;
|
end;
|
||||||
else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));
|
|
||||||
end;
|
end;
|
||||||
else raw_value1=raw_value;
|
else raw_value1=raw_value;
|
||||||
|
|
||||||
|
|||||||
@@ -51,8 +51,8 @@
|
|||||||
> )
|
> )
|
||||||
|
|
||||||
@param [in] inds The input table with query values
|
@param [in] inds The input table with query values
|
||||||
@param [out] outref= The output fileref to contain the filter clause. Will
|
@param [out] outref= (filter) The output fileref to contain the filter clause.
|
||||||
be created (or replaced).
|
Will be created (or replaced).
|
||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
@li mp_filtercheck.sas
|
@li mp_filtercheck.sas
|
||||||
|
|||||||
@@ -44,9 +44,10 @@
|
|||||||
mp_coretable.sas as follows: `mp_coretable(LOCKTABLE)`.
|
mp_coretable.sas as follows: `mp_coretable(LOCKTABLE)`.
|
||||||
@param [in] maxkeytable= (0) Optional permanent reference table used for
|
@param [in] maxkeytable= (0) Optional permanent reference table used for
|
||||||
retained key tracking. Described in mp_retainedkey.sas.
|
retained key tracking. Described in mp_retainedkey.sas.
|
||||||
@param [in] mdebug= set to 1 to enable DEBUG messages
|
@param [in] mdebug= (1) set to 1 to enable DEBUG messages
|
||||||
@param [out] outresult= The result table with the FILTER_RK
|
@param [out] outresult= (work.result) The result table with the FILTER_RK
|
||||||
@param [out] outquery= The original query, taken as extract after table load
|
@param [out] outquery= (work.query) The original query, taken as extract
|
||||||
|
after table load
|
||||||
|
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
|
|||||||
@@ -12,14 +12,15 @@
|
|||||||
|
|
||||||
%mp_getcols(sashelp.airline,outds=work.myds)
|
%mp_getcols(sashelp.airline,outds=work.myds)
|
||||||
|
|
||||||
@param ds The dataset from which to obtain column metadata
|
@param [in] ds The dataset from which to obtain column metadata
|
||||||
@param outds= (work.cols) The output dataset to create. Sample data:
|
@param [out] outds= (work.cols) The output dataset to create. Sample data:
|
||||||
|NAME:$32.|LENGTH:best.|VARNUM:best.|LABEL:$256.|FMTNAME:$32.|FORMAT:$49.|TYPE:$1.|DDTYPE:$9.|
|
|NAME:$32.|LENGTH:best.|VARNUM:best.|LABEL:$256.|FMTNAME:$32.|FORMAT:$49.|TYPE:$1.|DDTYPE:$9.|
|
||||||
|---|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|---|
|
||||||
|`AIR `|`8 `|`2 `|`international airline travel (thousands) `|` `|`8. `|`N `|`NUMERIC `|
|
|`AIR `|`8 `|`2 `|`international airline travel (thousands) `|` `|`8. `|`N `|`NUMERIC `|
|
||||||
|`DATE `|`8 `|`1 `|`DATE `|`MONYY `|`MONYY. `|`N `|`DATE `|
|
|`DATE `|`8 `|`1 `|`DATE `|`MONYY `|`MONYY. `|`N `|`DATE `|
|
||||||
|`REGION `|`3 `|`3 `|`REGION `|` `|`$3. `|`C `|`CHARACTER `|
|
|`REGION `|`3 `|`3 `|`REGION `|` `|`$3. `|`C `|`CHARACTER `|
|
||||||
|
|
||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
@li mf_getvarlist.sas
|
@li mf_getvarlist.sas
|
||||||
@li mm_getcols.sas
|
@li mm_getcols.sas
|
||||||
|
|||||||
@@ -94,8 +94,11 @@ create table &outds as
|
|||||||
/**
|
/**
|
||||||
* We cannot apply this clause to the underlying dictionary table. See:
|
* We cannot apply this clause to the underlying dictionary table. See:
|
||||||
* https://communities.sas.com/t5/SAS-Programming/Unexpected-Where-Clause-behaviour-in-dictionary-TABLE/m-p/771554#M244867
|
* https://communities.sas.com/t5/SAS-Programming/Unexpected-Where-Clause-behaviour-in-dictionary-TABLE/m-p/771554#M244867
|
||||||
|
* cannot use`where calculated libref="&lib"` either as it will STILL execute
|
||||||
|
* all the underlying constraint queries, causing exception errors in some
|
||||||
|
* cases: https://github.com/sasjs/core/issues/283
|
||||||
*/
|
*/
|
||||||
where calculated libref="&lib"
|
where a.TABLE_CATALOG="&lib"
|
||||||
%if "&ds" ne "" %then %do;
|
%if "&ds" ne "" %then %do;
|
||||||
and upcase(a.TABLE_NAME)="&ds"
|
and upcase(a.TABLE_NAME)="&ds"
|
||||||
and upcase(b.TABLE_NAME)="&ds"
|
and upcase(b.TABLE_NAME)="&ds"
|
||||||
|
|||||||
@@ -23,10 +23,10 @@
|
|||||||
@li mf_getquotedstr.sas
|
@li mf_getquotedstr.sas
|
||||||
@li mp_getconstraints.sas
|
@li mp_getconstraints.sas
|
||||||
|
|
||||||
@param liblist= Space seperated list of librefs to take as
|
@param [in] liblist= (SASHELP) Space seperated list of librefs to take as
|
||||||
input (Default=SASHELP)
|
input
|
||||||
@param outref= Fileref to contain the DBML (Default=getdbml)
|
@param [out] outref= (getdbml) Fileref to contain the DBML
|
||||||
@param showlog= set to YES to show the DBML in the log (Default is NO)
|
@param [in] showlog= (NO) set to YES to show the DBML in the log
|
||||||
|
|
||||||
@version 9.3
|
@version 9.3
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|||||||
@@ -22,16 +22,21 @@
|
|||||||
@li mf_getvarcount.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 [in] libref Libref of the library to create DDL for. Should already
|
||||||
@param ds dataset to create ddl for (optional)
|
be assigned.
|
||||||
@param fref= the fileref to which to _append_ the DDL. If it does not exist,
|
@param [in] ds dataset to create ddl for (optional)
|
||||||
it will be created.
|
@param [in] fref= (getddl) the fileref to which to _append_ the DDL. If it
|
||||||
@param flavour= The type of DDL to create (default=SAS). Supported=TSQL
|
does not exist, it will be created.
|
||||||
@param showlog= Set to YES to show the DDL in the log
|
@param [in] flavour= (SAS) The type of DDL to create. Options:
|
||||||
@param schema= Choose a preferred schema name (default is to use actual schema
|
@li SAS
|
||||||
,else libref)
|
@li TSQL
|
||||||
@param applydttm= for non SAS DDL, choose if columns are created with native
|
|
||||||
datetime2 format or regular decimal type
|
@param [in]showlog= (NO) Set to YES to show the DDL in the log
|
||||||
|
@param [in] schema= () Choose a preferred schema name (default is to use
|
||||||
|
actual schema, else libref)
|
||||||
|
@param [in] applydttm= (NO) For non SAS DDL, choose if columns are created
|
||||||
|
with native datetime2 format or regular decimal type
|
||||||
|
|
||||||
@version 9.3
|
@version 9.3
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
@@ -130,13 +135,13 @@ run;
|
|||||||
|
|
||||||
%local x curds;
|
%local x curds;
|
||||||
%if &flavour=SAS %then %do;
|
%if &flavour=SAS %then %do;
|
||||||
data _null_;
|
|
||||||
file &fref mod;
|
|
||||||
put "/* SAS Flavour DDL for %upcase(&libref).&curds */";
|
|
||||||
put "proc sql;";
|
|
||||||
run;
|
|
||||||
%do x=1 %to %sysfunc(countw(&dsnlist));
|
%do x=1 %to %sysfunc(countw(&dsnlist));
|
||||||
%let curds=%scan(&dsnlist,&x);
|
%let curds=%scan(&dsnlist,&x);
|
||||||
|
data _null_;
|
||||||
|
file &fref mod;
|
||||||
|
put "/* SAS Flavour DDL for %upcase(&libref).&curds */";
|
||||||
|
put "proc sql;";
|
||||||
|
run;
|
||||||
data _null_;
|
data _null_;
|
||||||
file &fref mod;
|
file &fref mod;
|
||||||
length lab $1024 typ $20;
|
length lab $1024 typ $20;
|
||||||
|
|||||||
@@ -7,10 +7,15 @@
|
|||||||
Formats are taken from the library / dataset reference and / or a static
|
Formats are taken from the library / dataset reference and / or a static
|
||||||
format list.
|
format list.
|
||||||
|
|
||||||
|
Note - the source for this information is the dictionary.formats table. This
|
||||||
|
cannot show formats that are not already declared in the FMTSEARCH path.
|
||||||
|
|
||||||
Example usage:
|
Example usage:
|
||||||
|
|
||||||
%mp_getformats(lib=sashelp,ds=prdsale,outsummary=work.dictable)
|
%mp_getformats(lib=sashelp,ds=prdsale,outsummary=work.dictable)
|
||||||
|
|
||||||
|
%mp_getformats(fmtlist=FORMAT1 $FORMAT2 @INFMT3,outsummary=work.table2)
|
||||||
|
|
||||||
@param [in] lib= (0) The libref for which to return formats.
|
@param [in] lib= (0) The libref for which to return formats.
|
||||||
@todo Enable exporting of formats for an entire library
|
@todo Enable exporting of formats for an entire library
|
||||||
@param [in] ds= (0) The dataset from which to obtain format definitions
|
@param [in] ds= (0) The dataset from which to obtain format definitions
|
||||||
@@ -49,7 +54,9 @@ https://support.sas.com/documentation/cdl/en/proc/61895/HTML/default/viewer.htm#
|
|||||||
|
|
||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
|
@li mf_getfmtlist.sas
|
||||||
@li mp_applyformats.sas
|
@li mp_applyformats.sas
|
||||||
|
@li mp_cntlout.sas
|
||||||
@li mp_getformats.test.sas
|
@li mp_getformats.test.sas
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@@ -66,7 +73,7 @@ https://support.sas.com/documentation/cdl/en/proc/61895/HTML/default/viewer.htm#
|
|||||||
|
|
||||||
%local i fmt allfmts tempds fmtcnt;
|
%local i fmt allfmts tempds fmtcnt;
|
||||||
|
|
||||||
%if "&fmtlist" ne "0" %then %do i=1 %to %sysfunc(countw(&fmtlist,,%str( )));
|
%if "&fmtlist" ne "0" %then %do i=1 %to %sysfunc(countw(&fmtlist,%str( )));
|
||||||
/* ensure format list contains format _name_ only */
|
/* ensure format list contains format _name_ only */
|
||||||
%let fmt=%scan(&fmtlist,&i,%str( ));
|
%let fmt=%scan(&fmtlist,&i,%str( ));
|
||||||
%let fmt=%mf_getfmtname(&fmt);
|
%let fmt=%mf_getfmtname(&fmt);
|
||||||
@@ -90,8 +97,7 @@ https://support.sas.com/documentation/cdl/en/proc/61895/HTML/default/viewer.htm#
|
|||||||
proc sql;
|
proc sql;
|
||||||
create table &outsummary as
|
create table &outsummary as
|
||||||
select * from dictionary.formats
|
select * from dictionary.formats
|
||||||
where fmtname in (%mf_getquotedstr(&allfmts,quote=D))
|
where fmtname in (%mf_getquotedstr(&allfmts,quote=D));
|
||||||
and fmttype='F';
|
|
||||||
|
|
||||||
%if "&outdetail" ne "0" %then %do;
|
%if "&outdetail" ne "0" %then %do;
|
||||||
/* ensure base table always exists */
|
/* ensure base table always exists */
|
||||||
@@ -115,6 +121,10 @@ create table &outsummary as
|
|||||||
data &tempds;
|
data &tempds;
|
||||||
if 0 then set &outdetail;
|
if 0 then set &outdetail;
|
||||||
set &tempds;
|
set &tempds;
|
||||||
|
/* set fmtrow (position of record within the format) */
|
||||||
|
by type fmtname notsorted;
|
||||||
|
if first.fmtname then fmtrow=1;
|
||||||
|
else fmtrow+1;
|
||||||
run;
|
run;
|
||||||
proc append base=&outdetail data=&tempds ;
|
proc append base=&outdetail data=&tempds ;
|
||||||
run;
|
run;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
@param [out] outds= (work.mp_getpk) The name of the output table to create.
|
@param [out] outds= (work.mp_getpk) The name of the output table to create.
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_existfeature.sas
|
||||||
@li mf_getengine.sas
|
@li mf_getengine.sas
|
||||||
@li mf_getschema.sas
|
@li mf_getschema.sas
|
||||||
@li mp_dropmembers.sas
|
@li mp_dropmembers.sas
|
||||||
@@ -229,7 +230,12 @@ create table work.&tabs1 as select
|
|||||||
libname as libref
|
libname as libref
|
||||||
,upcase(memname) as dsn
|
,upcase(memname) as dsn
|
||||||
,memtype
|
,memtype
|
||||||
|
%if %mf_existfeature(DBMS_MEMTYPE)=1 %then %do;
|
||||||
,dbms_memtype
|
,dbms_memtype
|
||||||
|
%end;
|
||||||
|
%else %do;
|
||||||
|
,'n/a' as dbms_memtype format=$32.
|
||||||
|
%end;
|
||||||
,typemem
|
,typemem
|
||||||
,memlabel
|
,memlabel
|
||||||
,nvar
|
,nvar
|
||||||
|
|||||||
45
base/mp_gitadd.sas
Normal file
45
base/mp_gitadd.sas
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Stages files in a GIT repo
|
||||||
|
@details Uses the output dataset from mp_gitstatus.sas to determine the files
|
||||||
|
that should be staged.
|
||||||
|
|
||||||
|
If `STAGED ne "TRUE"` then the file is staged.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
%let dir=%sysfunc(pathname(work))/core;
|
||||||
|
%let repo=https://github.com/sasjs/core;
|
||||||
|
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&dir));
|
||||||
|
%mf_writefile(&dir/somefile.txt,l1=some content)
|
||||||
|
%mf_deletefile(&dir/package.json)
|
||||||
|
%mp_gitstatus(&dir,outds=work.gitstatus)
|
||||||
|
|
||||||
|
%mp_gitadd(&dir,inds=work.gitstatus)
|
||||||
|
|
||||||
|
@param [in] gitdir The directory containing the GIT repository
|
||||||
|
@param [in] inds= (work.mp_gitadd) The input dataset with the list of files
|
||||||
|
to stage. Will accept the output from mp_gitstatus(), else just use a table
|
||||||
|
with the following columns:
|
||||||
|
@li path $1024 - relative path to the file in the repo
|
||||||
|
@li staged $32 - whether the file is staged (TRUE or FALSE)
|
||||||
|
@li status $64 - either new, deleted, or modified
|
||||||
|
|
||||||
|
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
|
||||||
|
|
||||||
|
<h4> Related Files </h4>
|
||||||
|
@li mp_gitadd.test.sas
|
||||||
|
@li mp_gitstatus.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_gitadd(gitdir,inds=work.mp_gitadd,mdebug=0);
|
||||||
|
|
||||||
|
data _null_;
|
||||||
|
set &inds;
|
||||||
|
if STAGED ne "TRUE";
|
||||||
|
rc=git_index_add("&gitdir",cats(path),status);
|
||||||
|
if rc ne 0 or &mdebug=1 then put rc=;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%mend mp_gitadd;
|
||||||
104
base/mp_gitlog.sas
Normal file
104
base/mp_gitlog.sas
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Creates a dataset with the commit history of a local repository
|
||||||
|
@details Returns the commit history from a local repository. The name of the
|
||||||
|
branch is also returned.
|
||||||
|
|
||||||
|
More details here:
|
||||||
|
https://documentation.sas.com/doc/ko/pgmsascdc/v_033/lefunctionsref/n1qo5miyvry1nen111js203hlwrh.htm
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
%let gitdir=%sysfunc(pathname(work))/core;
|
||||||
|
%let repo=https://github.com/sasjs/core;
|
||||||
|
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&dir));
|
||||||
|
|
||||||
|
%mp_gitlog(&gitdir,outds=work.mp_gitlog)
|
||||||
|
|
||||||
|
@param [in] gitdir The directory containing the GIT repository
|
||||||
|
@param [in] filter= (BRANCHONLY) To return only the commits for the current
|
||||||
|
branch, use BRANCHONLY (the default). Anything else will return the entire
|
||||||
|
commit history.
|
||||||
|
@param [out] outds= (work.mp_gitlog) The output dataset to create.
|
||||||
|
All vars are $128 except `message` which is $4000.
|
||||||
|
@li author returns the author who submitted the commit.
|
||||||
|
@li children_ids returns a list of the children commit IDs
|
||||||
|
@li committer returns the name of the committer.
|
||||||
|
@li committer_email returns the email of the committer.
|
||||||
|
@li email returns the email of the commit author.
|
||||||
|
@li id returns the commit ID of the commit object.
|
||||||
|
@li in_current_branch returns "TRUE" or "FALSE" to indicate if the commit is
|
||||||
|
in the current branch.
|
||||||
|
@li message returns the commit message.
|
||||||
|
@li parent_ids returns a list of the parent commit IDs.
|
||||||
|
@li stash returns "TRUE" or "FALSE" to indicate if the commit is a stash
|
||||||
|
commit.
|
||||||
|
@li time returns the time of the commit as numeric string
|
||||||
|
@li commit_time_num time of the commit as numeric SAS datetime
|
||||||
|
@li commit_time_str the commit_time_num variable cast as string
|
||||||
|
|
||||||
|
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
|
||||||
|
@param [in] nobs= (0) Set to an integer greater than 0 to restrict the number
|
||||||
|
of rows returned
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_getgitbranch.sas
|
||||||
|
|
||||||
|
<h4> Related Files </h4>
|
||||||
|
@li mp_gitadd.sas
|
||||||
|
@li mp_gitreleaseinfo.sas
|
||||||
|
@li mp_gitstatus.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_gitlog(gitdir,outds=work.mp_gitlog,mdebug=0,filter=BRANCHONLY,nobs=0);
|
||||||
|
|
||||||
|
%local varlist i var;
|
||||||
|
%let varlist=author children_ids committer committer_email email id
|
||||||
|
in_current_branch parent_ids stash time ;
|
||||||
|
|
||||||
|
data &outds;
|
||||||
|
LENGTH gitdir branch $ 1024 message $4000 &varlist $128 commit_time_num 8.
|
||||||
|
commit_time_str $32;
|
||||||
|
call missing (of _all_);
|
||||||
|
branch="%mf_getgitbranch(&gitdir)";
|
||||||
|
gitdir=symget('gitdir');
|
||||||
|
rc=git_status_free(trim(gitdir));
|
||||||
|
if rc=-1 then do;
|
||||||
|
put "The libgit2 library is unavailable and no Git operations can be used.";
|
||||||
|
put "See: https://stackoverflow.com/questions/74082874";
|
||||||
|
stop;
|
||||||
|
end;
|
||||||
|
else if rc=-2 then do;
|
||||||
|
put "The libgit2 library is available, but the status function failed.";
|
||||||
|
put "See the log for details.";
|
||||||
|
stop;
|
||||||
|
end;
|
||||||
|
entries=git_commit_log(trim(gitdir));
|
||||||
|
do n=1 to entries;
|
||||||
|
|
||||||
|
%do i=1 %to %sysfunc(countw(&varlist message));
|
||||||
|
%let var=%scan(&varlist message,&i,%str( ));
|
||||||
|
rc=git_commit_get(n,trim(gitdir),"&var",&var);
|
||||||
|
%end;
|
||||||
|
/* convert unix time to SAS time - https://4gl.uk/corelink0 */
|
||||||
|
/* Number of seconds between 01JAN1960 and 01JAN1970: 315619200 */
|
||||||
|
format commit_time_num datetime19.;
|
||||||
|
commit_time_num=sum(input(cats(time),best.),315619200);
|
||||||
|
commit_time_str=put(commit_time_num,datetime19.);
|
||||||
|
%if &mdebug=1 %then %do;
|
||||||
|
putlog (_all_)(=);
|
||||||
|
%end;
|
||||||
|
if "&filter"="BRANCHONLY" then do;
|
||||||
|
if cats(in_current_branch)='TRUE' then output;
|
||||||
|
end;
|
||||||
|
else output;
|
||||||
|
%if &nobs>0 %then %do;
|
||||||
|
if n ge &nobs then stop;
|
||||||
|
%end;
|
||||||
|
end;
|
||||||
|
rc=git_commit_free(trim(gitdir));
|
||||||
|
keep gitdir branch &varlist message time commit_time_num commit_time_str;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%mend mp_gitlog;
|
||||||
74
base/mp_gitreleaseinfo.sas
Normal file
74
base/mp_gitreleaseinfo.sas
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Pulls latest release info from a GIT repository
|
||||||
|
@details Useful for grabbing the latest version number or other attributes
|
||||||
|
from a GIT server. Supported providers are GitLab and GitHub. Pull requests
|
||||||
|
are welcome if you'd like to see additional providers!
|
||||||
|
|
||||||
|
Note that each provider provides slightly different JSON output. Therefore
|
||||||
|
the macro simply extracts the JSON and assigns the libname (using the JSON
|
||||||
|
engine).
|
||||||
|
|
||||||
|
Example usage (eg, to grab latest release version from github):
|
||||||
|
|
||||||
|
%mp_gitreleaseinfo(GITHUB,sasjs/core,outlib=mylibref)
|
||||||
|
|
||||||
|
data _null_;
|
||||||
|
set mylibref.root;
|
||||||
|
putlog TAG_NAME=;
|
||||||
|
run;
|
||||||
|
|
||||||
|
@param [in] provider The GIT provider for the release info. Accepted values:
|
||||||
|
@li GITLAB
|
||||||
|
@li GITHUB - Tables include root, assets, author, alldata
|
||||||
|
@param [in] project The link to the repository. This has different formats
|
||||||
|
depending on the vendor:
|
||||||
|
@li GITHUB - org/repo, eg sasjs/core
|
||||||
|
@li GITLAB - project, eg 1343223
|
||||||
|
@param [in] server= (0) If your repo is self-hosted, then provide the domain
|
||||||
|
here. Otherwise it will default to the provider domain (eg gitlab.com).
|
||||||
|
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
|
||||||
|
@param [out] outlib= (GITREL) The JSON-engine libref to be created, which will
|
||||||
|
point at the returned JSON
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_getuniquefileref.sas
|
||||||
|
|
||||||
|
<h4> Related Files </h4>
|
||||||
|
@li mp_gitreleaseinfo.test.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_gitreleaseinfo(provider,project,server=0,outlib=GITREL,mdebug=0);
|
||||||
|
%local url fref;
|
||||||
|
|
||||||
|
%let provider=%upcase(&provider);
|
||||||
|
|
||||||
|
%if &provider=GITHUB %then %do;
|
||||||
|
%if "&server"="0" %then %let server=https://api.github.com;
|
||||||
|
%let url=&server/repos/&project/releases/latest;
|
||||||
|
%end;
|
||||||
|
%else %if &provider=GITLAB %then %do;
|
||||||
|
%if "&server"="0" %then %let server=https://gitlab.com;
|
||||||
|
%let url=&server/api/v4/projects/&project/releases;
|
||||||
|
%end;
|
||||||
|
|
||||||
|
%let fref=%mf_getuniquefileref();
|
||||||
|
|
||||||
|
proc http method='GET' out=&fref url="&url";
|
||||||
|
%if &mdebug=1 %then %do;
|
||||||
|
debug level = 3;
|
||||||
|
%end;
|
||||||
|
run;
|
||||||
|
|
||||||
|
libname &outlib JSON fileref=&fref;
|
||||||
|
|
||||||
|
%if &mdebug=1 %then %do;
|
||||||
|
data _null_;
|
||||||
|
infile &fref;
|
||||||
|
input;
|
||||||
|
putlog _infile_;
|
||||||
|
run;
|
||||||
|
%end;
|
||||||
|
|
||||||
|
%mend mp_gitreleaseinfo;
|
||||||
67
base/mp_gitstatus.sas
Normal file
67
base/mp_gitstatus.sas
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Creates a dataset with the output from `GIT_STATUS()`
|
||||||
|
@details Uses `git_status()` to fetch the number of changed files, then
|
||||||
|
iterates with `git_status_get()`, inserting all attributes into an output
|
||||||
|
dataset.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
%let dir=%sysfunc(pathname(work))/core;
|
||||||
|
%let repo=https://github.com/sasjs/core;
|
||||||
|
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&dir));
|
||||||
|
%mf_writefile(&dir/somefile.txt,l1=some content)
|
||||||
|
%mf_deletefile(&dir/package.json)
|
||||||
|
|
||||||
|
%mp_gitstatus(&dir,outds=work.gitstatus)
|
||||||
|
|
||||||
|
More info on these functions is in this [helpful paper]
|
||||||
|
(https://www.sas.com/content/dam/SAS/support/en/sas-global-forum-proceedings/2019/3057-2019.pdf)
|
||||||
|
by Danny Zimmerman.
|
||||||
|
|
||||||
|
@param [in] gitdir The directory containing the GIT repository
|
||||||
|
@param [out] outds= (work.git_status) The output dataset to create. Vars:
|
||||||
|
@li gitdir $1024 - directory of repo
|
||||||
|
@li path $1024 - relative path to the file in the repo
|
||||||
|
@li staged $32 - whether the file is staged (TRUE or FALSE)
|
||||||
|
@li status $64 - either new, deleted, or modified
|
||||||
|
@li cnt - number of files
|
||||||
|
@li n - the "nth" file in the list from git_status()
|
||||||
|
|
||||||
|
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
|
||||||
|
|
||||||
|
<h4> Related Files </h4>
|
||||||
|
@li mp_gitstatus.test.sas
|
||||||
|
@li mp_gitadd.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_gitstatus(gitdir,outds=work.mp_gitstatus,mdebug=0);
|
||||||
|
|
||||||
|
data &outds;
|
||||||
|
LENGTH gitdir path $ 1024 STATUS $ 64 STAGED $ 32;
|
||||||
|
call missing (of _all_);
|
||||||
|
gitdir=symget('gitdir');
|
||||||
|
cnt=git_status(trim(gitdir));
|
||||||
|
if cnt=-1 then do;
|
||||||
|
put "The libgit2 library is unavailable and no Git operations can be used.";
|
||||||
|
put "See: https://stackoverflow.com/questions/74082874";
|
||||||
|
end;
|
||||||
|
else if cnt=-2 then do;
|
||||||
|
put "The libgit2 library is available, but the status function failed.";
|
||||||
|
put "See the log for details.";
|
||||||
|
end;
|
||||||
|
else do n=1 to cnt;
|
||||||
|
rc=GIT_STATUS_GET(n,gitdir,'PATH',path);
|
||||||
|
rc=GIT_STATUS_GET(n,gitdir,'STAGED',staged);
|
||||||
|
rc=GIT_STATUS_GET(n,gitdir,'STATUS',status);
|
||||||
|
output;
|
||||||
|
%if &mdebug=1 %then %do;
|
||||||
|
putlog (_all_)(=);
|
||||||
|
%end;
|
||||||
|
end;
|
||||||
|
rc=git_status_free(trim(gitdir));
|
||||||
|
drop rc cnt;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%mend mp_gitstatus;
|
||||||
@@ -22,15 +22,15 @@
|
|||||||
list;
|
list;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
@param file= (0) The file to perform the substitution on
|
@param [in] file= (0) The file to perform the substitution on
|
||||||
@param patternvar= A macro variable containing the Lua
|
@param [in] patternvar= A macro variable containing the Lua
|
||||||
[pattern](https://www.lua.org/pil/20.2.html) to search for. Due to the use
|
[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 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.
|
of the macro variable containing the string, rather than the value itself.
|
||||||
@param replacevar= The name of the macro variable containing the replacement
|
@param [in] replacevar= ()
|
||||||
_string_.
|
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
|
@param [out] outfile= (0) The file to write the output to.
|
||||||
is overwritten in-place.
|
If zero, then the file is overwritten in-place.
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li ml_gsubfile.sas
|
@li ml_gsubfile.sas
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
%mp_guesspk(sashelp.class,outds=classpks)
|
%mp_guesspk(sashelp.class,outds=classpks)
|
||||||
|
|
||||||
@param [in] baseds The dataset to analyse
|
@param [in] baseds The dataset to analyse
|
||||||
@param [out] outds= The output dataset to contain the possible PKs
|
@param [out] outds= (mp_guesspk) Output dataset to contain the possible PKs
|
||||||
@param [in] max_guesses= (3) The total number of possible primary keys to
|
@param [in] max_guesses= (3) The total number of possible primary keys to
|
||||||
generate. A table may have multiple (unlikely) PKs, so no need to list them
|
generate. A table may have multiple (unlikely) PKs, so no need to list them
|
||||||
all.
|
all.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
put hashkey=;
|
put hashkey=;
|
||||||
run;
|
run;
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_getattrn.sas
|
@li mf_getattrn.sas
|
||||||
@@ -21,11 +21,12 @@
|
|||||||
|
|
||||||
<h4> Related Files </h4>
|
<h4> Related Files </h4>
|
||||||
@li mp_hashdataset.test.sas
|
@li mp_hashdataset.test.sas
|
||||||
|
@li mp_hashdirectory.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 [in] salt= () Provide a salt (could be, for instance, the dataset name)
|
||||||
@param [in] iftrue= A condition under which the macro should be executed.
|
@param [in] iftrue= (1=1) A condition under which the macro should be executed
|
||||||
@param [out] outds= (work.mf_hashdataset) The output dataset to create. This
|
@param [out] outds= (work._data_) 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)
|
||||||
|hashkey:$32.|
|
|hashkey:$32.|
|
||||||
|
|||||||
164
base/mp_hashdirectory.sas
Normal file
164
base/mp_hashdirectory.sas
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Returns a unique hash for each file in a directory
|
||||||
|
@details Hashes each file in each directory, and then hashes the hashes to
|
||||||
|
create a hash for each directory also.
|
||||||
|
|
||||||
|
This makes use of the new `hashing_file()` and `hashing` functions, available
|
||||||
|
since 9.4m6. Interestingly, those functions can be used in pure macro, eg:
|
||||||
|
|
||||||
|
%put %sysfunc(hashing_file(md5,/path/to/file.blob,0));
|
||||||
|
|
||||||
|
Actual usage:
|
||||||
|
|
||||||
|
%let fpath=/some/directory;
|
||||||
|
|
||||||
|
%mp_hashdirectory(&fpath,outds=myhash,maxdepth=2)
|
||||||
|
|
||||||
|
data _null_;
|
||||||
|
set work.myhash;
|
||||||
|
put (_all_)(=);
|
||||||
|
run;
|
||||||
|
|
||||||
|
Whilst files are hashed in their entirety, the logic for creating a folder
|
||||||
|
hash is as follows:
|
||||||
|
|
||||||
|
@li Sort the files by filename (case sensitive, uppercase then lower)
|
||||||
|
@li Take the first 100 hashes, concatenate and hash
|
||||||
|
@li Concatenate this hash with another 100 hashes and hash again
|
||||||
|
@li Continue until the end of the folder. This is the folder hash
|
||||||
|
@li If a folder contains other folders, start from the bottom of the tree -
|
||||||
|
the folder hashes cascade upwards so you know immediately if there is a
|
||||||
|
change in a sub/sub directory
|
||||||
|
@li If a subfolder has no content (empty) then it is ignored. No hash created.
|
||||||
|
@li If the file is empty, it is also ignored / no hash created.
|
||||||
|
@li If the target directory (&inloc) is empty, &outds will also be empty
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mp_dirlist.sas
|
||||||
|
|
||||||
|
<h4> Related Files </h4>
|
||||||
|
@li mp_hashdataset.sas
|
||||||
|
@li mp_hashdirectory.test.sas
|
||||||
|
@li mp_md5.sas
|
||||||
|
|
||||||
|
@param [in] inloc Full filepath of the file to be hashed (unquoted)
|
||||||
|
@param [in] iftrue= (1=1) A condition under which the macro should be executed
|
||||||
|
@param [in] maxdepth= (0) Set to a positive integer to indicate the level of
|
||||||
|
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
|
||||||
|
recursion, set to MAX.
|
||||||
|
@param [in] method= (MD5) the hashing method to use. Available options:
|
||||||
|
@li MD5
|
||||||
|
@li SH1
|
||||||
|
@li SHA256
|
||||||
|
@li SHA384
|
||||||
|
@li SHA512
|
||||||
|
@li CRC32
|
||||||
|
@param [out] outds= (work.mp_hashdirectory) The output dataset. Contains:
|
||||||
|
@li directory - the parent folder
|
||||||
|
@li file_hash - the hash output
|
||||||
|
@li hash_duration - how long the hash took (first hash always takes longer)
|
||||||
|
@li file_path - /full/path/to/each/file.ext
|
||||||
|
@li file_or_folder - contains either "file" or "folder"
|
||||||
|
@li level - the depth of the directory (top level is 0)
|
||||||
|
|
||||||
|
@version 9.4m6
|
||||||
|
@author Allan Bowe
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_hashdirectory(inloc,
|
||||||
|
outds=work.mp_hashdirectory,
|
||||||
|
method=MD5,
|
||||||
|
maxdepth=0,
|
||||||
|
iftrue=%str(1=1)
|
||||||
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
%local curlevel tempds maxlevel;
|
||||||
|
|
||||||
|
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||||
|
|
||||||
|
/* get the directory listing */
|
||||||
|
%mp_dirlist(path=&inloc, outds=&outds, maxdepth=&maxdepth, showparent=YES)
|
||||||
|
|
||||||
|
/* create the hashes */
|
||||||
|
data &outds;
|
||||||
|
set &outds (rename=(filepath=file_path));
|
||||||
|
length FILE_HASH $32 HASH_DURATION 8;
|
||||||
|
keep directory file_hash hash_duration file_path file_or_folder level;
|
||||||
|
|
||||||
|
ts=datetime();
|
||||||
|
if file_or_folder='file' then do;
|
||||||
|
/* if file is empty, hashing_file will break - so ignore / delete */
|
||||||
|
length fname val $8;
|
||||||
|
drop fname val fid is_empty;
|
||||||
|
rc=filename(fname,file_path);
|
||||||
|
fid=fopen(fname);
|
||||||
|
if fid > 0 then do;
|
||||||
|
rc=fread(fid);
|
||||||
|
is_empty=fget(fid,val);
|
||||||
|
end;
|
||||||
|
rc=fclose(fid);
|
||||||
|
rc=filename(fname);
|
||||||
|
if is_empty ne 0 then delete;
|
||||||
|
else file_hash=hashing_file("&method",cats(file_path),0);
|
||||||
|
end;
|
||||||
|
hash_duration=datetime()-ts;
|
||||||
|
run;
|
||||||
|
|
||||||
|
proc sort data=&outds ;
|
||||||
|
by descending level directory file_path;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%let maxlevel=0;
|
||||||
|
data _null_;
|
||||||
|
set &outds;
|
||||||
|
call symputx('maxlevel',level,'l');
|
||||||
|
stop;
|
||||||
|
run;
|
||||||
|
|
||||||
|
/* now hash the hashes to populate folder hashes, starting from the bottom */
|
||||||
|
%do curlevel=&maxlevel %to 0 %by -1;
|
||||||
|
data work._data_ (keep=directory file_hash);
|
||||||
|
set &outds;
|
||||||
|
where level=&curlevel;
|
||||||
|
by descending level directory file_path;
|
||||||
|
length str $32767 tmp_hash $32;
|
||||||
|
retain str tmp_hash ;
|
||||||
|
/* reset vars when starting a new directory */
|
||||||
|
if first.directory then do;
|
||||||
|
str='';
|
||||||
|
tmp_hash='';
|
||||||
|
i=0;
|
||||||
|
end;
|
||||||
|
/* hash each chunk of 100 file paths */
|
||||||
|
i+1;
|
||||||
|
str=cats(str,file_hash);
|
||||||
|
if mod(i,100)=0 or last.directory then do;
|
||||||
|
tmp_hash=hashing("&method",cats(tmp_hash,str));
|
||||||
|
str='';
|
||||||
|
end;
|
||||||
|
/* output the hash at directory level */
|
||||||
|
if last.directory then do;
|
||||||
|
file_hash=tmp_hash;
|
||||||
|
output;
|
||||||
|
end;
|
||||||
|
if last.level then stop;
|
||||||
|
run;
|
||||||
|
%let tempds=&syslast;
|
||||||
|
/* join the hash back into the main table */
|
||||||
|
proc sql undo_policy=none;
|
||||||
|
create table &outds as
|
||||||
|
select a.directory
|
||||||
|
,coalesce(b.file_hash,a.file_hash) as file_hash
|
||||||
|
,a.hash_duration
|
||||||
|
,a.file_path
|
||||||
|
,a.file_or_folder
|
||||||
|
,a.level
|
||||||
|
from &outds a
|
||||||
|
left join &tempds b
|
||||||
|
on a.file_path=b.directory
|
||||||
|
order by level desc, directory, file_path;
|
||||||
|
drop table &tempds;
|
||||||
|
%end;
|
||||||
|
|
||||||
|
%mend mp_hashdirectory;
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
@file mp_jsonout.sas
|
@file mp_jsonout.sas
|
||||||
@brief Writes JSON in SASjs format to a fileref
|
@brief Writes JSON in SASjs format to a fileref
|
||||||
@details PROC JSON is faster but will produce errs like the ones below if
|
@details This macro can be used to OPEN a JSON stream and send one or more
|
||||||
|
tables as arrays of rows, where each row can be an object or a nested array.
|
||||||
|
|
||||||
|
There are two engines available - DATASTEP or PROCJSON.
|
||||||
|
|
||||||
|
PROC JSON is fast but will produce errs like the ones below if
|
||||||
special chars are encountered.
|
special chars are encountered.
|
||||||
|
|
||||||
> (ERR)OR: Some code points did not transcode.
|
> (ERR)OR: Some code points did not transcode.
|
||||||
@@ -12,6 +17,10 @@
|
|||||||
|
|
||||||
If this happens, try running with ENGINE=DATASTEP.
|
If this happens, try running with ENGINE=DATASTEP.
|
||||||
|
|
||||||
|
The DATASTEP engine is used to handle special SAS missing numerics, and
|
||||||
|
can also convert entire datasets to formatted values. Output JSON is always
|
||||||
|
in UTF-8.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
filename tmp temp;
|
filename tmp temp;
|
||||||
@@ -19,7 +28,7 @@
|
|||||||
|
|
||||||
%mp_jsonout(OPEN,jref=tmp)
|
%mp_jsonout(OPEN,jref=tmp)
|
||||||
%mp_jsonout(OBJ,class,jref=tmp)
|
%mp_jsonout(OBJ,class,jref=tmp)
|
||||||
%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=YES)
|
%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)
|
||||||
%mp_jsonout(CLOSE,jref=tmp)
|
%mp_jsonout(CLOSE,jref=tmp)
|
||||||
|
|
||||||
data _null_;
|
data _null_;
|
||||||
@@ -46,12 +55,14 @@
|
|||||||
@li DATASTEP (more reliable when data has non standard characters)
|
@li DATASTEP (more reliable when data has non standard characters)
|
||||||
@param [in] missing= (NULL) Special numeric missing values can be sent as NULL
|
@param [in] missing= (NULL) Special numeric missing values can be sent as NULL
|
||||||
(eg `null`) or as STRING values (eg `".a"` or `".b"`)
|
(eg `null`) or as STRING values (eg `".a"` or `".b"`)
|
||||||
@param [in] showmeta= (NO) Set to YES to output metadata alongside each table,
|
@param [in] showmeta= (N) Set to Y to output metadata alongside each table,
|
||||||
such as the column formats and types. The metadata is contained inside an
|
such as the column formats and types. The metadata is contained inside an
|
||||||
object with the same name as the table but prefixed with a dollar sign - ie,
|
object with the same name as the table but prefixed with a dollar sign - ie,
|
||||||
`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
|
`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
|
||||||
|
@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows
|
||||||
|
that should be converted to JSON
|
||||||
|
|
||||||
<h4> Related Macros <h4>
|
<h4> Related Files </h4>
|
||||||
@li mp_ds2fmtds.sas
|
@li mp_ds2fmtds.sas
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@@ -59,14 +70,16 @@
|
|||||||
@source https://github.com/sasjs/core
|
@source https://github.com/sasjs/core
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y
|
%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y
|
||||||
,engine=DATASTEP
|
,engine=DATASTEP
|
||||||
,missing=NULL
|
,missing=NULL
|
||||||
,showmeta=NO
|
,showmeta=N
|
||||||
|
,maxobs=MAX
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%local tempds colinfo fmtds i numcols;
|
%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval
|
||||||
|
tmpds1 tmpds2 tmpds3 tmpds4;
|
||||||
%let numcols=0;
|
%let numcols=0;
|
||||||
|
%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);
|
||||||
|
|
||||||
%if &action=OPEN %then %do;
|
%if &action=OPEN %then %do;
|
||||||
options nobomfile;
|
options nobomfile;
|
||||||
@@ -75,9 +88,23 @@
|
|||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
%else %if (&action=ARR or &action=OBJ) %then %do;
|
%else %if (&action=ARR or &action=OBJ) %then %do;
|
||||||
|
/* force variable names to always be uppercase in the JSON */
|
||||||
options validvarname=upcase;
|
options validvarname=upcase;
|
||||||
data _null_; file &jref encoding='utf-8' mod;
|
/* To avoid issues with _webout on EBI - such as encoding diffs and truncation
|
||||||
|
(https://support.sas.com/kb/49/325.html) we use temporary files */
|
||||||
|
filename _sjs1 temp lrecl=200 ;
|
||||||
|
data _null_; file _sjs1 encoding='utf-8';
|
||||||
put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";
|
put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";
|
||||||
|
run;
|
||||||
|
/* now write to _webout 1 char at a time */
|
||||||
|
data _null_;
|
||||||
|
infile _sjs1 lrecl=1 recfm=n;
|
||||||
|
file &jref mod lrecl=1 recfm=n;
|
||||||
|
input sourcechar $char1. @@;
|
||||||
|
format sourcechar hex2.;
|
||||||
|
put sourcechar char1. @@;
|
||||||
|
run;
|
||||||
|
filename _sjs1 clear;
|
||||||
|
|
||||||
/* grab col defs */
|
/* grab col defs */
|
||||||
proc contents noprint data=&ds
|
proc contents noprint data=&ds
|
||||||
@@ -88,7 +115,7 @@
|
|||||||
by varnum;
|
by varnum;
|
||||||
run;
|
run;
|
||||||
/* move meta to mac vars */
|
/* move meta to mac vars */
|
||||||
data _null_;
|
data &colinfo;
|
||||||
if _n_=1 then call symputx('numcols',nobs,'l');
|
if _n_=1 then call symputx('numcols',nobs,'l');
|
||||||
set &colinfo end=last nobs=nobs;
|
set &colinfo end=last nobs=nobs;
|
||||||
name=upcase(name);
|
name=upcase(name);
|
||||||
@@ -99,7 +126,6 @@
|
|||||||
if format='' then fmt=cats('$',length,'.');
|
if format='' then fmt=cats('$',length,'.');
|
||||||
else if formatl=0 then fmt=cats(format,'.');
|
else if formatl=0 then fmt=cats(format,'.');
|
||||||
else fmt=cats(format,formatl,'.');
|
else fmt=cats(format,formatl,'.');
|
||||||
newlen=max(formatl,length);
|
|
||||||
end;
|
end;
|
||||||
else do;
|
else do;
|
||||||
typelong='num';
|
typelong='num';
|
||||||
@@ -107,23 +133,26 @@
|
|||||||
else if formatl=0 then fmt=cats(format,'.');
|
else if formatl=0 then fmt=cats(format,'.');
|
||||||
else if formatd=0 then fmt=cats(format,formatl,'.');
|
else if formatd=0 then fmt=cats(format,formatl,'.');
|
||||||
else fmt=cats(format,formatl,'.',formatd);
|
else fmt=cats(format,formatl,'.',formatd);
|
||||||
/* needs to be wide, for datetimes etc */
|
|
||||||
newlen=max(length,formatl,24);
|
|
||||||
end;
|
end;
|
||||||
/* 32 char unique name */
|
/* 32 char unique name */
|
||||||
newname='sasjs'!!substr(cats(put(md5(name),$hex32.)),1,27);
|
newname='sasjs'!!substr(cats(put(md5(name),$hex32.)),1,27);
|
||||||
|
|
||||||
call symputx(cats('name',_n_),name,'l');
|
call symputx(cats('name',_n_),name,'l');
|
||||||
call symputx(cats('newname',_n_),newname,'l');
|
call symputx(cats('newname',_n_),newname,'l');
|
||||||
call symputx(cats('len',_n_),newlen,'l');
|
|
||||||
call symputx(cats('length',_n_),length,'l');
|
call symputx(cats('length',_n_),length,'l');
|
||||||
call symputx(cats('fmt',_n_),fmt,'l');
|
call symputx(cats('fmt',_n_),fmt,'l');
|
||||||
call symputx(cats('type',_n_),type,'l');
|
call symputx(cats('type',_n_),type,'l');
|
||||||
call symputx(cats('typelong',_n_),typelong,'l');
|
call symputx(cats('typelong',_n_),typelong,'l');
|
||||||
call symputx(cats('label',_n_),coalescec(label,name),'l');
|
call symputx(cats('label',_n_),coalescec(label,name),'l');
|
||||||
|
/* overwritten when fmt=Y and a custom format exists in catalog */
|
||||||
|
if typelong='num' then call symputx(cats('fmtlen',_n_),200,'l');
|
||||||
|
else call symputx(cats('fmtlen',_n_),min(32767,ceil((length+10)*1.5)),'l');
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
|
%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
|
||||||
|
proc sql;
|
||||||
|
select count(*) into: lastobs from &ds;
|
||||||
|
%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));
|
||||||
|
|
||||||
%if &engine=PROCJSON %then %do;
|
%if &engine=PROCJSON %then %do;
|
||||||
%if &missing=STRING %then %do;
|
%if &missing=STRING %then %do;
|
||||||
@@ -131,13 +160,25 @@
|
|||||||
%put &sysmacroname: Switching to DATASTEP engine;
|
%put &sysmacroname: Switching to DATASTEP engine;
|
||||||
%goto datastep;
|
%goto datastep;
|
||||||
%end;
|
%end;
|
||||||
data &tempds;set &ds;
|
data &tempds;
|
||||||
|
set &ds;
|
||||||
|
&stmt_obs;
|
||||||
%if &fmt=N %then format _numeric_ best32.;;
|
%if &fmt=N %then format _numeric_ best32.;;
|
||||||
/* PRETTY is necessary to avoid line truncation in large files */
|
/* PRETTY is necessary to avoid line truncation in large files */
|
||||||
proc json out=&jref pretty
|
filename _sjs2 temp lrecl=131068 encoding='utf-8';
|
||||||
|
proc json out=_sjs2 pretty
|
||||||
%if &action=ARR %then nokeys ;
|
%if &action=ARR %then nokeys ;
|
||||||
;export &tempds / nosastags fmtnumeric;
|
;export &tempds / nosastags fmtnumeric;
|
||||||
run;
|
run;
|
||||||
|
/* send back to webout */
|
||||||
|
data _null_;
|
||||||
|
infile _sjs2 lrecl=1 recfm=n;
|
||||||
|
file &jref mod lrecl=1 recfm=n;
|
||||||
|
input sourcechar $char1. @@;
|
||||||
|
format sourcechar hex2.;
|
||||||
|
put sourcechar char1. @@;
|
||||||
|
run;
|
||||||
|
filename _sjs2 clear;
|
||||||
%end;
|
%end;
|
||||||
%else %if &engine=DATASTEP %then %do;
|
%else %if &engine=DATASTEP %then %do;
|
||||||
%datastep:
|
%datastep:
|
||||||
@@ -148,26 +189,99 @@
|
|||||||
%end;
|
%end;
|
||||||
|
|
||||||
%if &fmt=Y %then %do;
|
%if &fmt=Y %then %do;
|
||||||
data _data_;
|
/**
|
||||||
|
* Extract format definitions
|
||||||
|
* First, by getting library locations from dictionary.formats
|
||||||
|
* Then, by exporting the width using proc format
|
||||||
|
* Cannot use maxw from sashelp.vformat as not always populated
|
||||||
|
* Cannot use fmtinfo() as not supported in all flavours
|
||||||
|
*/
|
||||||
|
%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
|
||||||
|
%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
|
||||||
|
%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
|
||||||
|
%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
|
||||||
|
proc sql noprint;
|
||||||
|
create table &tmpds1 as
|
||||||
|
select cats(libname,'.',memname) as FMTCAT,
|
||||||
|
FMTNAME
|
||||||
|
from dictionary.formats
|
||||||
|
where fmttype='F' and libname is not null
|
||||||
|
and fmtname in (select format from &colinfo where format is not null)
|
||||||
|
order by 1;
|
||||||
|
create table &tmpds2(
|
||||||
|
FMTNAME char(32),
|
||||||
|
LENGTH num
|
||||||
|
);
|
||||||
|
%local catlist cat fmtlist i;
|
||||||
|
select distinct fmtcat into: catlist separated by ' ' from &tmpds1;
|
||||||
|
%do i=1 %to %sysfunc(countw(&catlist,%str( )));
|
||||||
|
%let cat=%scan(&catlist,&i,%str( ));
|
||||||
|
proc sql;
|
||||||
|
select distinct fmtname into: fmtlist separated by ' '
|
||||||
|
from &tmpds1 where fmtcat="&cat";
|
||||||
|
proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);
|
||||||
|
select &fmtlist;
|
||||||
|
run;
|
||||||
|
proc sql;
|
||||||
|
insert into &tmpds2 select distinct fmtname,length from &tmpds3;
|
||||||
|
%end;
|
||||||
|
|
||||||
|
proc sql;
|
||||||
|
create table &tmpds4 as
|
||||||
|
select a.*, b.length as MAXW
|
||||||
|
from &colinfo a
|
||||||
|
left join &tmpds2 b
|
||||||
|
on cats(a.format)=cats(upcase(b.fmtname))
|
||||||
|
order by a.varnum;
|
||||||
|
data _null_;
|
||||||
|
set &tmpds4;
|
||||||
|
if not missing(maxw);
|
||||||
|
call symputx(
|
||||||
|
cats('fmtlen',_n_),
|
||||||
|
/* vars need extra padding due to JSON escaping of special chars */
|
||||||
|
min(32767,ceil((max(length,maxw)+10)*1.5))
|
||||||
|
,'l'
|
||||||
|
);
|
||||||
|
run;
|
||||||
|
|
||||||
|
/* configure varlenchk - as we are explicitly shortening the variables */
|
||||||
|
%let optval=%sysfunc(getoption(varlenchk));
|
||||||
|
options varlenchk=NOWARN;
|
||||||
|
data _data_(compress=char);
|
||||||
|
/* shorten the new vars */
|
||||||
|
length
|
||||||
|
%do i=1 %to &numcols;
|
||||||
|
&&name&i $&&fmtlen&i
|
||||||
|
%end;
|
||||||
|
;
|
||||||
/* rename on entry */
|
/* rename on entry */
|
||||||
set &ds(rename=(
|
set &ds(rename=(
|
||||||
%do i=1 %to &numcols;
|
%do i=1 %to &numcols;
|
||||||
&&name&i=&&newname&i
|
&&name&i=&&newname&i
|
||||||
%end;
|
%end;
|
||||||
));
|
));
|
||||||
|
&stmt_obs;
|
||||||
|
|
||||||
|
drop
|
||||||
|
%do i=1 %to &numcols;
|
||||||
|
&&newname&i
|
||||||
|
%end;
|
||||||
|
;
|
||||||
%do i=1 %to &numcols;
|
%do i=1 %to &numcols;
|
||||||
length &&name&i $&&len&i;
|
|
||||||
%if &&typelong&i=num %then %do;
|
%if &&typelong&i=num %then %do;
|
||||||
&&name&i=left(put(&&newname&i,&&fmt&i));
|
&&name&i=cats(put(&&newname&i,&&fmt&i));
|
||||||
%end;
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
&&name&i=put(&&newname&i,&&fmt&i);
|
&&name&i=put(&&newname&i,&&fmt&i);
|
||||||
%end;
|
%end;
|
||||||
drop &&newname&i;
|
|
||||||
%end;
|
%end;
|
||||||
if _error_ then call symputx('syscc',1012);
|
if _error_ then do;
|
||||||
|
call symputx('syscc',1012);
|
||||||
|
stop;
|
||||||
|
end;
|
||||||
run;
|
run;
|
||||||
%let fmtds=&syslast;
|
%let fmtds=&syslast;
|
||||||
|
options varlenchk=&optval;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
proc format; /* credit yabwon for special null removal */
|
proc format; /* credit yabwon for special null removal */
|
||||||
@@ -186,8 +300,8 @@
|
|||||||
attrib _all_ label='';
|
attrib _all_ label='';
|
||||||
%do i=1 %to &numcols;
|
%do i=1 %to &numcols;
|
||||||
%if &&typelong&i=char or &fmt=Y %then %do;
|
%if &&typelong&i=char or &fmt=Y %then %do;
|
||||||
length &&name&i $32767;
|
length &&name&i $&&fmtlen&i...;
|
||||||
format &&name&i $32767.;
|
format &&name&i $&&fmtlen&i...;
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
%if &fmt=Y %then %do;
|
%if &fmt=Y %then %do;
|
||||||
@@ -196,10 +310,11 @@
|
|||||||
%else %do;
|
%else %do;
|
||||||
set &ds;
|
set &ds;
|
||||||
%end;
|
%end;
|
||||||
|
&stmt_obs;
|
||||||
format _numeric_ bart.;
|
format _numeric_ bart.;
|
||||||
%do i=1 %to &numcols;
|
%do i=1 %to &numcols;
|
||||||
%if &&typelong&i=char or &fmt=Y %then %do;
|
%if &&typelong&i=char or &fmt=Y %then %do;
|
||||||
if findc(&&name&i,'"\'!!'0A0D09000E0F01021011'x) then do;
|
if findc(&&name&i,'"\'!!'0A0D09000E0F010210111A'x) then do;
|
||||||
&&name&i='"'!!trim(
|
&&name&i='"'!!trim(
|
||||||
prxchange('s/"/\\"/',-1, /* double quote */
|
prxchange('s/"/\\"/',-1, /* double quote */
|
||||||
prxchange('s/\x0A/\n/',-1, /* new line */
|
prxchange('s/\x0A/\n/',-1, /* new line */
|
||||||
@@ -212,18 +327,18 @@
|
|||||||
prxchange('s/\x02/\\u0002/',-1, /* STX */
|
prxchange('s/\x02/\\u0002/',-1, /* STX */
|
||||||
prxchange('s/\x10/\\u0010/',-1, /* DLE */
|
prxchange('s/\x10/\\u0010/',-1, /* DLE */
|
||||||
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
|
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
|
||||||
|
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
|
||||||
prxchange('s/\\/\\\\/',-1,&&name&i)
|
prxchange('s/\\/\\\\/',-1,&&name&i)
|
||||||
))))))))))))!!'"';
|
)))))))))))))!!'"';
|
||||||
end;
|
end;
|
||||||
else &&name&i=quote(cats(&&name&i));
|
else &&name&i=quote(cats(&&name&i));
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
/* write to temp loc to avoid _webout truncation
|
filename _sjs3 temp lrecl=131068 ;
|
||||||
- https://support.sas.com/kb/49/325.html */
|
data _null_;
|
||||||
filename _sjs temp lrecl=131068 encoding='utf-8';
|
file _sjs3 encoding='utf-8';
|
||||||
data _null_; file _sjs lrecl=131068 encoding='utf-8' mod ;
|
|
||||||
if _n_=1 then put "[";
|
if _n_=1 then put "[";
|
||||||
set &tempds;
|
set &tempds;
|
||||||
if _n_>1 then put "," @; put
|
if _n_>1 then put "," @; put
|
||||||
@@ -234,36 +349,35 @@
|
|||||||
"&&name&i"n /* name literal for reserved variable names */
|
"&&name&i"n /* name literal for reserved variable names */
|
||||||
%end;
|
%end;
|
||||||
%if &action=ARR %then "]" ; %else "}" ; ;
|
%if &action=ARR %then "]" ; %else "}" ; ;
|
||||||
/* now write the long strings to _webout 1 byte at a time */
|
|
||||||
|
/* close out the table */
|
||||||
data _null_;
|
data _null_;
|
||||||
length filein 8 fileid 8;
|
file _sjs3 mod encoding='utf-8';
|
||||||
filein=fopen("_sjs",'I',1,'B');
|
put ']';
|
||||||
fileid=fopen("&jref",'A',1,'B');
|
|
||||||
rec='20'x;
|
|
||||||
do while(fread(filein)=0);
|
|
||||||
rc=fget(filein,rec,1);
|
|
||||||
rc=fput(fileid, rec);
|
|
||||||
rc=fwrite(fileid);
|
|
||||||
end;
|
|
||||||
/* close out the table */
|
|
||||||
rc=fput(fileid, "]");
|
|
||||||
rc=fwrite(fileid);
|
|
||||||
rc=fclose(filein);
|
|
||||||
rc=fclose(fileid);
|
|
||||||
run;
|
run;
|
||||||
filename _sjs clear;
|
data _null_;
|
||||||
|
infile _sjs3 lrecl=1 recfm=n;
|
||||||
|
file &jref mod lrecl=1 recfm=n;
|
||||||
|
input sourcechar $char1. @@;
|
||||||
|
format sourcechar hex2.;
|
||||||
|
put sourcechar char1. @@;
|
||||||
|
run;
|
||||||
|
filename _sjs3 clear;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
proc sql;
|
proc sql;
|
||||||
drop table &colinfo, &tempds;
|
drop table &colinfo, &tempds;
|
||||||
|
|
||||||
%if &showmeta=YES %then %do;
|
%if %substr(&showmeta,1,1)=Y %then %do;
|
||||||
data _null_; file &jref encoding='utf-8' mod;
|
filename _sjs4 temp lrecl=131068 encoding='utf-8';
|
||||||
|
data _null_;
|
||||||
|
file _sjs4;
|
||||||
|
length label $350;
|
||||||
put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";
|
put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";
|
||||||
do i=1 to &numcols;
|
do i=1 to &numcols;
|
||||||
name=quote(trim(symget(cats('name',i))));
|
name=quote(trim(symget(cats('name',i))));
|
||||||
format=quote(trim(symget(cats('fmt',i))));
|
format=quote(trim(symget(cats('fmt',i))));
|
||||||
label=quote(trim(symget(cats('label',i))));
|
label=quote(prxchange('s/\\/\\\\/',-1,trim(symget(cats('label',i)))));
|
||||||
length=quote(trim(symget(cats('length',i))));
|
length=quote(trim(symget(cats('length',i))));
|
||||||
type=quote(trim(symget(cats('typelong',i))));
|
type=quote(trim(symget(cats('typelong',i))));
|
||||||
if i>1 then put "," @@;
|
if i>1 then put "," @@;
|
||||||
@@ -272,6 +386,15 @@
|
|||||||
end;
|
end;
|
||||||
put '}}';
|
put '}}';
|
||||||
run;
|
run;
|
||||||
|
/* send back to webout */
|
||||||
|
data _null_;
|
||||||
|
infile _sjs4 lrecl=1 recfm=n;
|
||||||
|
file &jref mod lrecl=1 recfm=n;
|
||||||
|
input sourcechar $char1. @@;
|
||||||
|
format sourcechar hex2.;
|
||||||
|
put sourcechar char1. @@;
|
||||||
|
run;
|
||||||
|
filename _sjs4 clear;
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
|
|||||||
@@ -24,19 +24,19 @@
|
|||||||
@li mf_trimstr.sas
|
@li mf_trimstr.sas
|
||||||
@li mp_ds2cards.sas
|
@li mp_ds2cards.sas
|
||||||
|
|
||||||
@param [in] lib= Library in which to convert all datasets
|
@param [in] lib= () Library in which to convert all datasets
|
||||||
@param [out] outloc= Location in which to store output. Defaults to WORK
|
@param [out] outloc= (%sysfunc(pathname(work))) Location in which to store
|
||||||
library. No quotes.
|
output. No quotes.
|
||||||
@param [out] outfile= Optional output file NAME - if provided, then will create
|
@param [out] outfile= (0) Optional output file NAME - if provided, then
|
||||||
a single output file instead of one file per input table.
|
will create a single output file instead of one file per input table.
|
||||||
@param [in] maxobs= limit output to the first <code>maxobs</code> observations
|
@param [in] maxobs= (max) limit output to the first <code>maxobs</code> rows
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mp_lib2cards(lib=
|
%macro mp_lib2cards(lib=
|
||||||
,outloc=%sysfunc(pathname(work)) /* without trailing slash */
|
,outloc=%sysfunc(pathname(work))
|
||||||
,maxobs=max
|
,maxobs=max
|
||||||
,random_sample=NO
|
,random_sample=NO
|
||||||
,outfile=0
|
,outfile=0
|
||||||
|
|||||||
@@ -34,18 +34,20 @@
|
|||||||
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages and preserve outputs
|
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages and preserve outputs
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mddl_sas_cntlout.sas
|
|
||||||
@li mf_getuniquename.sas
|
@li mf_getuniquename.sas
|
||||||
@li mf_nobs.sas
|
@li mf_nobs.sas
|
||||||
@li mp_abort.sas
|
@li mp_abort.sas
|
||||||
|
@li mp_aligndecimal.sas
|
||||||
@li mp_cntlout.sas
|
@li mp_cntlout.sas
|
||||||
@li mp_lockanytable.sas
|
@li mp_lockanytable.sas
|
||||||
|
@li mp_md5.sas
|
||||||
@li mp_storediffs.sas
|
@li mp_storediffs.sas
|
||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
@li mddl_dc_difftable.sas
|
@li mddl_dc_difftable.sas
|
||||||
@li mddl_dc_locktable.sas
|
@li mddl_dc_locktable.sas
|
||||||
@li mp_loadformat.test.sas
|
@li mp_loadformat.test.1.sas
|
||||||
|
@li mp_loadformat.test.2.sas
|
||||||
@li mp_lockanytable.sas
|
@li mp_lockanytable.sas
|
||||||
@li mp_stackdiffs.sas
|
@li mp_stackdiffs.sas
|
||||||
|
|
||||||
@@ -67,7 +69,7 @@
|
|||||||
);
|
);
|
||||||
/* set up local macro variables and temporary tables (with a prefix) */
|
/* set up local macro variables and temporary tables (with a prefix) */
|
||||||
%local err msg prefix dslist i var fmtlist ibufsize;
|
%local err msg prefix dslist i var fmtlist ibufsize;
|
||||||
%let dslist=base_fmts template inlibds ds1 stagedata storediffs;
|
%let dslist=base_fmts template inlibds ds1 stagedata storediffs del1 del2;
|
||||||
%if &outds_add=0 %then %let dslist=&dslist outds_add;
|
%if &outds_add=0 %then %let dslist=&dslist outds_add;
|
||||||
%if &outds_del=0 %then %let dslist=&dslist outds_del;
|
%if &outds_del=0 %then %let dslist=&dslist outds_del;
|
||||||
%if &outds_mod=0 %then %let dslist=&dslist outds_mod;
|
%if &outds_mod=0 %then %let dslist=&dslist outds_mod;
|
||||||
@@ -78,13 +80,6 @@
|
|||||||
%let &var=%upcase(&prefix._&var);
|
%let &var=%upcase(&prefix._&var);
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
/*
|
|
||||||
format values can be up to 32767 wide. SQL joins on such a wide column can
|
|
||||||
cause buffer issues. Update ibufsize and reset at the end.
|
|
||||||
*/
|
|
||||||
%let ibufsize=%sysfunc(getoption(ibufsize));
|
|
||||||
options ibufsize=32767 ;
|
|
||||||
|
|
||||||
/* in DC, format catalogs maybe specified in the libds with a -FC extension */
|
/* in DC, format catalogs maybe specified in the libds with a -FC extension */
|
||||||
%let libcat=%scan(&libcat,1,-);
|
%let libcat=%scan(&libcat,1,-);
|
||||||
|
|
||||||
@@ -134,29 +129,62 @@ run;
|
|||||||
* First, extract only relevant formats from the catalog
|
* First, extract only relevant formats from the catalog
|
||||||
*/
|
*/
|
||||||
proc sql noprint;
|
proc sql noprint;
|
||||||
select distinct upcase(fmtname) into: fmtlist separated by ' ' from &libds;
|
select distinct
|
||||||
|
case
|
||||||
|
when type='N' then upcase(fmtname)
|
||||||
|
when type='C' then cats('$',upcase(fmtname))
|
||||||
|
when type='I' then cats('@',upcase(fmtname))
|
||||||
|
when type='J' then cats('@$',upcase(fmtname))
|
||||||
|
else "&sysmacroname:UNHANDLED"
|
||||||
|
end
|
||||||
|
into: fmtlist separated by ' '
|
||||||
|
from &libds;
|
||||||
|
|
||||||
%mp_cntlout(libcat=&libcat,fmtlist=&fmtlist,cntlout=&base_fmts)
|
%mp_cntlout(libcat=&libcat,fmtlist=&fmtlist,cntlout=&base_fmts)
|
||||||
|
|
||||||
|
/* get a hash of the row */
|
||||||
|
%local cvars nvars;
|
||||||
|
%let cvars=TYPE FMTNAME START END LABEL PREFIX FILL SEXCL EEXCL HLO DECSEP
|
||||||
|
DIG3SEP DATATYPE LANGUAGE;
|
||||||
|
%let nvars=FMTROW MIN MAX DEFAULT LENGTH FUZZ MULT NOEDIT;
|
||||||
|
data &base_fmts/note2err;
|
||||||
|
set &base_fmts;
|
||||||
|
fmthash=%mp_md5(cvars=&cvars, nvars=&nvars);
|
||||||
|
run;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure input table and base_formats have consistent lengths and types
|
* Ensure input table and base_formats have consistent lengths and types
|
||||||
*/
|
*/
|
||||||
%mddl_sas_cntlout(libds=&template)
|
data &inlibds/nonote2err;
|
||||||
data &inlibds;
|
length &delete_col $3 FMTROW 8 start end label $32767;
|
||||||
length &delete_col $3;
|
if 0 then set &base_fmts;
|
||||||
if 0 then set &template;
|
|
||||||
set &libds;
|
set &libds;
|
||||||
|
by type fmtname notsorted;
|
||||||
if &delete_col='' then &delete_col='No';
|
if &delete_col='' then &delete_col='No';
|
||||||
fmtname=upcase(fmtname);
|
fmtname=upcase(fmtname);
|
||||||
|
type=upcase(type);
|
||||||
if missing(type) then do;
|
if missing(type) then do;
|
||||||
if substr(fmtname,1,1)='$' then type='C';
|
if substr(fmtname,1,1)='@' then do;
|
||||||
else type='N';
|
if substr(fmtname,2,1)='$' then type='J';
|
||||||
|
else type='I';
|
||||||
|
end;
|
||||||
|
else do;
|
||||||
|
if substr(fmtname,1,1)='$' then type='C';
|
||||||
|
else type='N';
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
if type='N' then do;
|
if type in ('N','I') then do;
|
||||||
start=cats(start);
|
%mp_aligndecimal(start,width=16)
|
||||||
end=cats(end);
|
%mp_aligndecimal(end,width=16)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
/* update row marker - retain new var as fmtrow may already be in libds */
|
||||||
|
if first.fmtname then row=1;
|
||||||
|
else row+1;
|
||||||
|
drop row;
|
||||||
|
fmtrow=row;
|
||||||
|
|
||||||
|
fmthash=%mp_md5(cvars=&cvars, nvars=&nvars);
|
||||||
run;
|
run;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -167,23 +195,10 @@ create table &outds_add(drop=&delete_col) as
|
|||||||
select a.*
|
select a.*
|
||||||
from &inlibds a
|
from &inlibds a
|
||||||
left join &base_fmts b
|
left join &base_fmts b
|
||||||
on a.fmtname=b.fmtname
|
on a.type=b.type and a.fmtname=b.fmtname and a.fmtrow=b.fmtrow
|
||||||
and a.start=b.start
|
|
||||||
where b.fmtname is null
|
where b.fmtname is null
|
||||||
and upcase(a.&delete_col) ne "YES"
|
and upcase(a.&delete_col) ne "YES"
|
||||||
order by fmtname, start;;
|
order by type, fmtname, fmtrow;
|
||||||
|
|
||||||
/**
|
|
||||||
* Identify deleted records
|
|
||||||
*/
|
|
||||||
create table &outds_del(drop=&delete_col) as
|
|
||||||
select a.*
|
|
||||||
from &inlibds a
|
|
||||||
inner join &base_fmts b
|
|
||||||
on a.fmtname=b.fmtname
|
|
||||||
and a.start=b.start
|
|
||||||
where upcase(a.&delete_col)="YES"
|
|
||||||
order by fmtname, start;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identify modified records
|
* Identify modified records
|
||||||
@@ -192,12 +207,40 @@ create table &outds_mod (drop=&delete_col) as
|
|||||||
select a.*
|
select a.*
|
||||||
from &inlibds a
|
from &inlibds a
|
||||||
inner join &base_fmts b
|
inner join &base_fmts b
|
||||||
on a.fmtname=b.fmtname
|
on a.type=b.type and a.fmtname=b.fmtname and a.fmtrow=b.fmtrow
|
||||||
and a.start=b.start
|
|
||||||
where upcase(a.&delete_col) ne "YES"
|
where upcase(a.&delete_col) ne "YES"
|
||||||
order by fmtname, start;
|
and a.fmthash ne b.fmthash
|
||||||
|
order by type, fmtname, fmtrow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify deleted records
|
||||||
|
*/
|
||||||
|
create table &outds_del(drop=&delete_col) as
|
||||||
|
select a.*
|
||||||
|
from &inlibds a
|
||||||
|
inner join &base_fmts b
|
||||||
|
on a.type=b.type and a.fmtname=b.fmtname and a.fmtrow=b.fmtrow
|
||||||
|
where upcase(a.&delete_col)="YES"
|
||||||
|
order by type, fmtname, fmtrow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify fully deleted formats (where every record is removed)
|
||||||
|
* These require to be explicitly deleted in proc format
|
||||||
|
* del1 - identify _partial_ deletes
|
||||||
|
* del2 - exclude these, and also formats that come with _additions_
|
||||||
|
*/
|
||||||
|
create table &del1 as
|
||||||
|
select a.*
|
||||||
|
from &base_fmts a
|
||||||
|
left join &outds_del b
|
||||||
|
on a.type=b.type and a.fmtname=b.fmtname and a.fmtrow=b.fmtrow
|
||||||
|
where b.fmtrow is null;
|
||||||
|
|
||||||
|
create table &del2 as
|
||||||
|
select * from &outds_del
|
||||||
|
where cats(type,fmtname) not in (select cats(type,fmtname) from &outds_add)
|
||||||
|
and cats(type,fmtname) not in (select cats(type,fmtname) from &del1);
|
||||||
|
|
||||||
options ibufsize=&ibufsize;
|
|
||||||
|
|
||||||
%mp_abort(
|
%mp_abort(
|
||||||
iftrue=(&syscc ne 0)
|
iftrue=(&syscc ne 0)
|
||||||
@@ -206,19 +249,21 @@ options ibufsize=&ibufsize;
|
|||||||
)
|
)
|
||||||
|
|
||||||
%if &loadtarget=YES %then %do;
|
%if &loadtarget=YES %then %do;
|
||||||
|
/* new records plus base records that are not deleted or modified */
|
||||||
data &ds1;
|
data &ds1;
|
||||||
merge &base_fmts(in=base)
|
merge &base_fmts(in=base)
|
||||||
&outds_mod(in=mod)
|
&outds_mod(in=mod)
|
||||||
&outds_add(in=add)
|
&outds_add(in=add)
|
||||||
&outds_del(in=del);
|
&outds_del(in=del);
|
||||||
if not del and not mod;
|
if not del and not mod;
|
||||||
by fmtname start;
|
by type fmtname fmtrow;
|
||||||
run;
|
run;
|
||||||
|
/* add back the modified records */
|
||||||
data &stagedata;
|
data &stagedata;
|
||||||
set &ds1 &outds_mod;
|
set &ds1 &outds_mod;
|
||||||
run;
|
run;
|
||||||
proc sort;
|
proc sort;
|
||||||
by fmtname start;
|
by type fmtname fmtrow;
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
/* mp abort needs to run outside of conditional blocks */
|
/* mp abort needs to run outside of conditional blocks */
|
||||||
@@ -228,7 +273,7 @@ options ibufsize=&ibufsize;
|
|||||||
,msg=%str(SYSCC=&syscc prior to actual load)
|
,msg=%str(SYSCC=&syscc prior to actual load)
|
||||||
)
|
)
|
||||||
%if &loadtarget=YES %then %do;
|
%if &loadtarget=YES %then %do;
|
||||||
%if %mf_nobs(&stagedata)=0 %then %do;
|
%if %mf_nobs(&stagedata)=0 and %mf_nobs(&del2)=0 %then %do;
|
||||||
%put There are no changes to load in &libcat!;
|
%put There are no changes to load in &libcat!;
|
||||||
%return;
|
%return;
|
||||||
%end;
|
%end;
|
||||||
@@ -244,6 +289,22 @@ options ibufsize=&ibufsize;
|
|||||||
/* do the actual load */
|
/* do the actual load */
|
||||||
proc format lib=&libcat cntlin=&stagedata;
|
proc format lib=&libcat cntlin=&stagedata;
|
||||||
run;
|
run;
|
||||||
|
/* apply any full deletes */
|
||||||
|
%if %mf_nobs(&del2)>0 %then %do;
|
||||||
|
%local delfmtlist;
|
||||||
|
proc sql noprint;
|
||||||
|
select distinct case when type='N' then cats(fmtname,'.FORMAT')
|
||||||
|
when type='C' then cats(fmtname,'.FORMATC')
|
||||||
|
when type='J' then cats(fmtname,'.INFMTC')
|
||||||
|
when type='I' then cats(fmtname,'.INFMT')
|
||||||
|
else cats(fmtname,'.BADENTRY!!!') end
|
||||||
|
into: delfmtlist
|
||||||
|
separated by ' '
|
||||||
|
from &del2;
|
||||||
|
proc catalog catalog=&libcat;
|
||||||
|
delete &delfmtlist;
|
||||||
|
quit;
|
||||||
|
%end;
|
||||||
%if &locklibds ne 0 %then %do;
|
%if &locklibds ne 0 %then %do;
|
||||||
/* unlock the table */
|
/* unlock the table */
|
||||||
%mp_lockanytable(UNLOCK
|
%mp_lockanytable(UNLOCK
|
||||||
@@ -266,7 +327,7 @@ options ibufsize=&ibufsize;
|
|||||||
|
|
||||||
%mp_storediffs(&libcat-FC
|
%mp_storediffs(&libcat-FC
|
||||||
,&base_fmts
|
,&base_fmts
|
||||||
,FMTNAME START
|
,TYPE FMTNAME FMTROW
|
||||||
,delds=&outds_del
|
,delds=&outds_del
|
||||||
,modds=&outds_mod
|
,modds=&outds_mod
|
||||||
,appds=&outds_add
|
,appds=&outds_add
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ run;
|
|||||||
data _null_;
|
data _null_;
|
||||||
putlog 'NOTE-' / 'NOTE-';
|
putlog 'NOTE-' / 'NOTE-';
|
||||||
putlog "NOTE- &sysmacroname: table locked, waiting "@;
|
putlog "NOTE- &sysmacroname: table locked, waiting "@;
|
||||||
putlog "%sysfunc(sleep(&loop_inc)) seconds.. ";
|
putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";
|
||||||
putlog "NOTE- (iteration &x of &loops)";
|
putlog "NOTE- (iteration &x of &loops)";
|
||||||
putlog 'NOTE-' / 'NOTE-';
|
putlog 'NOTE-' / 'NOTE-';
|
||||||
run;
|
run;
|
||||||
@@ -194,32 +194,39 @@ run;
|
|||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
%else %if &ACTION=UNLOCK %then %do;
|
%else %if &ACTION=UNLOCK %then %do;
|
||||||
%local status;
|
%local status cnt;
|
||||||
|
%let cnt=0;
|
||||||
proc sql noprint;
|
proc sql noprint;
|
||||||
select LOCK_STATUS_CD into: status from &ctl_ds
|
select count(*) into: cnt from &ctl_ds where LOCK_LIB ="&lib" & LOCK_DS="&ds";
|
||||||
where LOCK_LIB ="&lib" and LOCK_DS="&ds";
|
%if &cnt=0 %then %do;
|
||||||
quit;
|
%put %str(WAR)NING: &lib..&ds was not previously locked in &ctl_ds!;
|
||||||
%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;
|
|
||||||
%if &status=LOCKED %then %do;
|
|
||||||
data _null_;
|
|
||||||
putlog "&sysmacroname: unlocking &lib..&ds:";
|
|
||||||
run;
|
|
||||||
proc sql;
|
|
||||||
update &ctl_ds
|
|
||||||
set LOCK_STATUS_CD='UNLOCKED'
|
|
||||||
, LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt
|
|
||||||
, LOCK_USER_NM="&user"
|
|
||||||
, LOCK_PID="&sysjobid"
|
|
||||||
, LOCK_REF="&ref"
|
|
||||||
where LOCK_LIB ="&lib" and LOCK_DS="&ds";
|
|
||||||
quit;
|
|
||||||
%end;
|
|
||||||
%else %if &status=UNLOCKED %then %do;
|
|
||||||
%put %str(WAR)NING: &lib..&ds is already unlocked!;
|
|
||||||
%end;
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;
|
select LOCK_STATUS_CD into: status from &ctl_ds
|
||||||
%let abortme=1;
|
where LOCK_LIB ="&lib" and LOCK_DS="&ds";
|
||||||
|
quit;
|
||||||
|
%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;
|
||||||
|
%if &status=LOCKED %then %do;
|
||||||
|
data _null_;
|
||||||
|
putlog "&sysmacroname: unlocking &lib..&ds:";
|
||||||
|
run;
|
||||||
|
proc sql;
|
||||||
|
update &ctl_ds
|
||||||
|
set LOCK_STATUS_CD='UNLOCKED'
|
||||||
|
, LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt
|
||||||
|
, LOCK_USER_NM="&user"
|
||||||
|
, LOCK_PID="&sysjobid"
|
||||||
|
, LOCK_REF="&ref"
|
||||||
|
where LOCK_LIB ="&lib" and LOCK_DS="&ds";
|
||||||
|
quit;
|
||||||
|
%end;
|
||||||
|
%else %if &status=UNLOCKED %then %do;
|
||||||
|
%put %str(WAR)NING: &lib..&ds is already unlocked!;
|
||||||
|
%end;
|
||||||
|
%else %do;
|
||||||
|
%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;
|
||||||
|
%let abortme=1;
|
||||||
|
%end;
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
@file
|
@file
|
||||||
@brief Generates an md5 expression for hashing a set of variables
|
@brief Generates an md5 expression for hashing a set of variables
|
||||||
@details This is the same algorithm used to hash records in
|
@details This is the same algorithm used to hash records in
|
||||||
[Data Controller for SAS](https://datacontroller.io) (free for up
|
[Data Controller for SAS](https://datacontroller.io).
|
||||||
to 5 users).
|
|
||||||
|
|
||||||
It is not designed to be efficient - it is designed to be effective,
|
It is not designed to be efficient - it is designed to be effective,
|
||||||
given the range of edge cases (large floating points, special missing
|
given the range of edge cases (large floating points, special missing
|
||||||
@@ -29,8 +28,8 @@
|
|||||||
@li Global option: `options dsoptions=nonote2err;`
|
@li Global option: `options dsoptions=nonote2err;`
|
||||||
@li Data step option: `data YOURLIB.YOURDATASET /nonote2err;`
|
@li Data step option: `data YOURLIB.YOURDATASET /nonote2err;`
|
||||||
|
|
||||||
@param cvars= Space seperated list of character variables
|
@param [in] cvars= () Space seperated list of character variables
|
||||||
@param nvars= Space seperated list of numeric variables
|
@param [in] nvars= () Space seperated list of numeric variables
|
||||||
|
|
||||||
<h4> Related Programs </h4>
|
<h4> Related Programs </h4>
|
||||||
@li mp_init.sas
|
@li mp_init.sas
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user