1
0
mirror of https://github.com/sasjs/core.git synced 2026-01-08 10:00:04 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Allan Bowe
f0a5d89016 build.sh build on 2022-02-24:21:33:11 2022-02-24 21:33:11 +00:00
371 changed files with 8052 additions and 19541 deletions

View File

@@ -117,54 +117,8 @@
"contributions": [ "contributions": [
"bug" "bug"
] ]
},
{
"login": "yabwon",
"name": "Bart Jablonski",
"avatar_url": "https://avatars.githubusercontent.com/u/9314894?v=4",
"profile": "https://github.com/yabwon",
"contributions": [
"code"
]
},
{
"login": "eltociear",
"name": "Ikko Ashimine",
"avatar_url": "https://avatars.githubusercontent.com/u/22633385?v=4",
"profile": "https://bandism.net/",
"contributions": [
"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"
} }

View File

@@ -1,8 +0,0 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node/.devcontainer/base.Dockerfile
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
ARG VARIANT="18-bullseye"
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
RUN apt-get update \
&& apt-get install -y doxygen

View File

@@ -1,26 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/typescript-node
{
"name": "Node.js & TypeScript",
"build": {
"dockerfile": "Dockerfile",
// Update 'VARIANT' to pick a Node version: 18, 16, 14.
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
"args": {
"VARIANT": "16-bullseye"
}
},
// Set *default* container specific settings.json values on container create.
"settings": {},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"SASjs.sasjs-for-vscode"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "npm i && npm i -g @sasjs/cli",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "node"
}

View File

@@ -1,12 +1,5 @@
#!/bin/bash #!/bin/bash
sasjs lint
# Ensure lint is passing
LINT=`sasjs lint`
if [[ "$LINT" != *"All matched files use @sasjs/lint code style!" ]]; then
echo "$LINT"
echo "To commit in spite of these warnings, use the -n parameter."
exit 1
fi
# Avoid commits to the master branch # Avoid commits to the master branch
BRANCH=`git rev-parse --abbrev-ref HEAD` BRANCH=`git rev-parse --abbrev-ref HEAD`

1
.gitattributes vendored
View File

@@ -1 +0,0 @@
* text=auto eol=lf

View File

@@ -27,6 +27,5 @@ 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. Commit using a [Conventional Commit](https://www.conventionalcommits.org) 4. Push and make a PR
5. Push and make a PR

3
.github/FUNDING.yml vendored
View File

@@ -1,3 +0,0 @@
# These are supported funding model platforms
custom: https://getalby.com/p/sasjs

View File

@@ -1,18 +0,0 @@
## 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

View File

@@ -1,25 +1,30 @@
# Client
client
tls-client
dev tun
# this will connect with whatever proto DNS tells us (https://community.openvpn.net/openvpn/ticket/934)
proto tcp
remote vpn.4gl.io 7494
resolv-retry infinite
cipher AES-256-CBC cipher AES-256-CBC
auth SHA256 setenv FORWARD_COMPATIBLE 1
script-security 2 client
keepalive 10 120 server-poll-timeout 4
remote-cert-tls server nobind
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 443 tcp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
dev tun
dev-type tun
ns-cert-type server
setenv opt tls-version-min 1.0 or-highest
reneg-sec 604800
sndbuf 0
rcvbuf 0
# NOTE: LZO commands are pushed by the Access Server at connect time.
# NOTE: The below line doesn't disable LZO.
comp-lzo no
verb 3
setenv PUSH_PEER_INFO
# 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

View File

@@ -13,54 +13,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@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:
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 - name: Semantic Release
uses: cycjimmy/semantic-release-action@v4 uses: cycjimmy/semantic-release-action@v2
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GH_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

View File

@@ -1,32 +0,0 @@
# 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

View File

@@ -12,7 +12,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [lts/hydrogen] node-version: [lts/fermium]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -39,39 +39,46 @@ 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-jammy.list sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-focal.list
sudo apt update sudo apt update
sudo apt install openvpn3=17~betaUb22042+jammy sudo apt install openvpn3
- 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 (aborts if errors found) - name: Check code style
run: npx @sasjs/cli lint run: npm run lint
- name: Add client - name: Add client
run: echo "CLIENT=${{secrets.SAS9_4GL_IO_CLIENT}}"> .env.server run: echo "CLIENT=${{secrets.CLIENT}}"> .env.viya
- name: Add secret
run: echo "SECRET=${{secrets.SECRET}}" >> .env.viya
- name: Add access token - name: Add access token
run: echo "ACCESS_TOKEN=${{secrets.SAS9_4GL_IO_ACCESS_TOKEN}}" >> .env.server run: echo "ACCESS_TOKEN=${{secrets.ACCESS_TOKEN}}" >> .env.viya
- name: Add refresh token - name: Add refresh token
run: echo "REFRESH_TOKEN=${{secrets.SAS9_4GL_IO_REFRESH_TOKEN}}" >> .env.server run: echo "REFRESH_TOKEN=${{secrets.REFRESH_TOKEN}}" >> .env.viya
- name: Build & Deploy Project to SAS server - name: Build Project
run: npx @sasjs/cli cbd -t server run: npm run build
- name: Run all tests - name: Run SASjs tests
run: npx @sasjs/cli test -t server run: npm run test
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
View File

@@ -10,7 +10,4 @@ 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*
~

View File

@@ -1,7 +1,7 @@
tasks: tasks:
- init: npm install -g npm - init: |
- command: npm i nvm install --lts
- command: npm i -g @sasjs/cli npm i -g @sasjs/cli
image: image:
file: .gitpod.dockerfile file: .gitpod.dockerfile
@@ -24,4 +24,4 @@ github:
# add a "Review in Gitpod" button to pull requests (defaults to false) # add a "Review in Gitpod" button to pull requests (defaults to false)
addBadge: false addBadge: false
# add a label once the prebuild is ready to pull requests (defaults to false) # add a label once the prebuild is ready to pull requests (defaults to false)
addLabel: prebuilt-in-gitpod addLabel: prebuilt-in-gitpod

View File

@@ -6,6 +6,7 @@ sasjs/
.github/ .github/
.git-hooks/ .git-hooks/
.vscode/ .vscode/
main.dox
make_singlefile.sh make_singlefile.sh
*.md *.md
.all-contributorsrc .all-contributorsrc

View File

@@ -1,15 +1,13 @@
{ {
"noTrailingSpaces": true, "noTrailingSpaces": true,
"noEncodedPasswords": true, "noEncodedPasswords": true,
"hasDoxygenHeader": true, "hasDoxygenHeader": true,
"hasMacroNameInMend": true, "hasMacroNameInMend": true,
"hasMacroParentheses": true, "hasMacroParentheses": true,
"lineEndings": "lf", "noNestedMacros": false,
"noGremlins": true, "noSpacesInFileNames": true,
"noNestedMacros": false, "maxLineLength": 300,
"noSpacesInFileNames": true, "lowerCaseFileNames": true,
"maxLineLength": 300, "noTabIndentation": true,
"lowerCaseFileNames": true, "indentationMultiple": 2
"noTabs": true, }
"indentationMultiple": 2
}

View File

@@ -6,7 +6,5 @@
"editor.rulers": [ "editor.rulers": [
80 80
], ],
"files.trimTrailingWhitespace": true, "files.trimTrailingWhitespace": true
"sasjs-for-vscode.target": "docsonly",
"sasjs-for-vscode.isLocal": true
} }

1
CNAME Normal file
View File

@@ -0,0 +1 @@
core.sasjs.io

161
README.md
View File

@@ -2,6 +2,8 @@
[![npm package][npm-image]][npm-url] [![npm package][npm-image]][npm-url]
[![Github Workflow][githubworkflow-image]][githubworkflow-url] [![Github Workflow][githubworkflow-image]][githubworkflow-url]
![npm](https://img.shields.io/npm/dt/@sasjs/core) ![npm](https://img.shields.io/npm/dt/@sasjs/core)
![Snyk Vulnerabilities for npm package](https://img.shields.io/snyk/vulnerabilities/npm/@sasjs/core)
[![License](https://img.shields.io/apm/l/atomic-design-ui.svg)](/LICENSE)
![GitHub top language](https://img.shields.io/github/languages/top/sasjs/core) ![GitHub top language](https://img.shields.io/github/languages/top/sasjs/core)
[![GitHub closed issues](https://img.shields.io/github/issues-closed-raw/sasjs/core)](https://github.com/sasjs/core/issues?q=is%3Aissue+is%3Aclosed) [![GitHub closed issues](https://img.shields.io/github/issues-closed-raw/sasjs/core)](https://github.com/sasjs/core/issues?q=is%3Aissue+is%3Aclosed)
[![GitHub issues](https://img.shields.io/github/issues-raw/sasjs/core)](https://github.com/sasjs/core/issues) [![GitHub issues](https://img.shields.io/github/issues-raw/sasjs/core)](https://github.com/sasjs/core/issues)
@@ -16,7 +18,7 @@
[dependency-url]:https://github.com/sasjs/core/blob/main/package.json [dependency-url]:https://github.com/sasjs/core/blob/main/package.json
Much quality. Many standards. The **Macro Core** library exists to save time and development effort! Herein ye shall find a veritable host of MIT-licenced, production quality SAS macros. These are a mix of tools, utilities, functions and code generators that are useful in the context of [Application Development](https://sasapps.io) on the SAS platform (eg https://datacontroller.io). [Contributions](https://github.com/sasjs/core/blob/main/.github/CONTRIBUTING.md) are welcome. Much quality. Many standards. The **Macro Core** library exists to save time and development effort! Herein ye shall find a veritable host of MIT-licenced, production quality SAS macros. These are a mix of tools, utilities, functions and code generators that are useful in the context of [Application Development](https://sasapps.io) on the SAS platform (eg https://datacontroller.io). [Contributions](https://github.com/sasjs/core/blob/main/CONTRIBUTING.md) are welcomed.
You can download and compile them all in just two lines of SAS code: You can download and compile them all in just two lines of SAS code:
@@ -29,30 +31,62 @@ Documentation: https://core.sasjs.io
## Components ## Components
### BASE folder (All Platforms) ### BASE library (All Platforms)
- 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 library (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 library (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.
### LUA folder ### META library (SAS9 only)
Macros used in SAS EBI, which connect to the metadata server.
- OS independent
- Metadata aware
- No X command
- Prefixes: _mm_
### SERVER library (@sasjs/server only)
These macros are used for building applications using [@sasjs/server](https://server.sasjs.io) - an open source REST API for Desktop SAS.
- OS independent
- @sasjs/server aware
- No X command
- Prefixes: _ms_
### VIYA library (Viya only)
Macros used for interfacing with SAS Viya.
- OS independent
- No X command
- Prefixes: _mv_, _mvf_
### METAX library (SAS9 only)
- OS specific
- Metadata aware
- X command enabled
- Prefixes: _mmw_,_mmu_,_mmx_
### LUA library
Wait - this is a macro library - what is LUA doing here? Well, it is a little known fact that you CAN run LUA within a SAS Macro. It has to be written to a text file with a `.lua` extension, from where you can `%include` it. So, without using the `proc lua` wrapper. Wait - this is a macro library - what is LUA doing here? Well, it is a little known fact that you CAN run LUA within a SAS Macro. It has to be written to a text file with a `.lua` extension, from where you can `%include` it. So, without using the `proc lua` wrapper.
@@ -70,63 +104,15 @@ endsubmit;
run; run;
``` ```
- Prefixes: `ml_` - Prefixes: _ml_
### META folder (SAS9 only)
Macros used in SAS EBI, which connect to the metadata server.
- OS independent
- Metadata aware
- No X command
- Prefixes: `mm_`
### METAX folder (SAS9 only)
- OS specific
- Metadata aware
- X command enabled
- Prefixes: `mmx_`
### 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.
- OS independent
- @sasjs/server aware
- No X command
- Prefixes: `ms_`
### VIYA folder (Viya only)
Macros used for interfacing with SAS Viya.
- OS independent
- No X command
- Prefixes: `mv_`, `mvf_`
### XPLATFORM folder (Viya, Meta, and Server)
Sometimes it is helpful to use a macro that can be used interchangeably regardless of the server type on which is is running (SASVIYA, SAS9, SASJS).
- OS independent
- No X command
- Prefixes: `mx_`
## Installation ## Installation
First, download the repo to a location your SAS system can access. Then update your sasautos path to include the components you wish to have available, eg: First, download the repo to a location your SAS system can access. Then update your sasautos path to include the components you wish to have available, eg:
```sas ```sas
%let repoloc=/your/path/core; options insert=(sasautos="/your/path/macrocore/base");
options insert=(sasautos="&repoloc/base"); options insert=(sasautos="/your/path/macrocore/meta");
options insert=(sasautos="&repoloc/ddl");
options insert=(sasautos="&repoloc/fcmp");
options insert=(sasautos="&repoloc/lua");
options insert=(sasautos="&repoloc/meta");
options insert=(sasautos="&repoloc/metax");
options insert=(sasautos="&repoloc/server");
options insert=(sasautos="&repoloc/viya");
options insert=(sasautos="&repoloc/xplatform");
``` ```
The above can be done directly in your sas program, via an autoexec, or an initialisation program. The above can be done directly in your sas program, via an autoexec, or an initialisation program.
@@ -156,7 +142,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
- _mp_ for macro procedures (which generate sas code) - _mp_ for macro procedures (which generate sas code)
- _ms_ for macro procedures that will only work with [@sasjs/server](https://github.com/sasjs/server) - _ms_ for macro procedures that will only work with [@sasjs/server](https://github.com/sasjs/server)
- _mv_ for macro procedures that will only work in Viya - _mv_ for macro procedures that will only work in Viya
- _mx_ for macros that work on Viya, SAS 9 EBI and SASjs Server - _mx_ for macros that are XCMD enabled (working on both windows and unix)
- follow verb-noun convention - follow verb-noun convention
- unix style line endings (lf) - unix style line endings (lf)
- individual lines should be no more than 80 characters long - individual lines should be no more than 80 characters long
@@ -212,14 +198,12 @@ 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.3 and later). If an earlier version is not supported, then the macro should say as such in the header documentation, and exit gracefully. - 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`).
- 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
We are currently on major release v4. Breaking changes should be marked with the [deprecated](https://www.doxygen.nl/manual/commands.html#cmddeprecated) doxygen tag. The following changes are planned when the next major/breaking release (v5) becomes necessary: We are currently on major release v4. Breaking changes should be marked with the [deprecated](https://www.doxygen.nl/manual/commands.html#cmddeprecated) doxygen tag. The following changes are planned when the next major/breaking release (v5) becomes necessary:
* mf_getuniquelibref.sas to have the deprecated maxtried parameter removed (no longer needed)
* mp_testservice.sas to be renamed as mp_execute.sas (as it doesn't actually test anything) * mp_testservice.sas to be renamed as mp_execute.sas (as it doesn't actually test anything)
* `insert_cmplib` option of mcf_xxx macros will be deprecated (the option is now checked automatically with value inserted only if needed) * `insert_cmplib` option of mcf_xxx macros will be deprecated (the option is now checked automatically with value inserted only if needed)
* mcf_xxx macros to have `wrap=` option defaulted to YES for convenience. Set this option explicitly to avoid issues. * mcf_xxx macros to have `wrap=` option defaulted to YES for convenience. Set this option explicitly to avoid issues.
@@ -232,23 +216,11 @@ If you find this library useful, please leave a [star](https://github.com/sasjs/
![](https://starchart.cc/sasjs/core.svg) ![](https://starchart.cc/sasjs/core.svg)
## Other SAS Repositories
The following repositories are also worth checking out:
* [chris-swenson/sasmacros](https://github.com/chris-swenson/sasmacros)
* [greg-wotton/sas-programs](https://github.com/greg-wootton/sas-programs)
* [KatjaGlassConsulting/SMILE-SmartSASMacros](https://github.com/KatjaGlassConsulting/SMILE-SmartSASMacros)
* [paul-canals/toolbox](https://github.com/paul-canals/toolbox)
* [rogerjdeangelis](https://github.com/rogerjdeangelis)
* [SASJedi/sas-macros](https://github.com/SASJedi/sas-macros)
* [scottbass/sas](https://github.com/scottbass/SAS)
* [xieliaing/SAS](https://github.com/xieliaing/SAS)
* [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 -->
[![All Contributors](https://img.shields.io/badge/all_contributors-15-orange.svg?style=flat-square)](#contributors-) [![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#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)):
@@ -256,29 +228,20 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
<!-- markdownlint-disable --> <!-- markdownlint-disable -->
<table> <table>
<tbody> <tr>
<tr> <td align="center"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Allan Bowe</b></sub></a><br /><a href="#business-allanbowe" title="Business development">💼</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Code">💻</a> <a href="#content-allanbowe" title="Content">🖋</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Documentation">📖</a> <a href="#infra-allanbowe" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-allanbowe" title="Maintenance">🚧</a> <a href="#mentoring-allanbowe" title="Mentoring">🧑‍🏫</a> <a href="#question-allanbowe" title="Answering Questions">💬</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3Aallanbowe" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Tests">⚠️</a></td>
<td align="center" 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/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/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://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/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://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://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://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://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://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://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> <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://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> <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>
<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/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/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/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/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> </tr>
<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>
<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 -->

9636
all.sas

File diff suppressed because it is too large Load Diff

View File

@@ -3,11 +3,6 @@
@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
@@ -17,7 +12,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)
)/des='ungraceful abort' /*STORE SOURCE*/; )/*/STORE SOURCE*/;
%if not(%eval(%unquote(&iftrue))) %then %return; %if not(%eval(%unquote(&iftrue))) %then %return;
@@ -29,4 +24,4 @@
%mend mf_abort; %mend mf_abort;
/** @endcond */ /** @endcond */

View File

@@ -1,31 +0,0 @@
/**
@file
@brief Deletes a physical file, if it exists
@details Usage:
%mf_writefile(&sasjswork/myfile.txt,l1=some content)
%mf_deletefile(&sasjswork/myfile.txt)
%mf_deletefile(&sasjswork/myfile.txt)
@param [in] file Full path to the target file
@returns The return code from the fdelete() invocation
<h4> Related Macros </h4>
@li mf_deletefile.test.sas
@li mf_writefile.sas
@version 9.2
@author Allan Bowe
**/
%macro mf_deletefile(file
)/*/STORE SOURCE*/;
%local rc fref;
%let rc= %sysfunc(filename(fref,&file));
%if %sysfunc(fdelete(&fref)) ne 0 %then %put %sysfunc(sysmsg());
%let rc= %sysfunc(filename(fref));
%mend mf_deletefile;

View File

@@ -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 [in] libds library.dataset @param libds library.dataset
@return output returns 1 or 0 @return output returns 1 or 0
<h4> Related Macros </h4> <h4> Related Macros </h4>

View File

@@ -9,17 +9,19 @@
%put %mf_existfeature(PROCLUA); %put %mf_existfeature(PROCLUA);
@param [in] feature The feature to detect. @param feature the feature to detect. Leave blank to list all in log.
@return output returns 1 or 0 (or -1 if not found) @return output returns 1 or 0 (or -1 if not found)
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getplatform.sas @li mf_getplatform.sas
@version 8 @version 8
@author Allan Bowe @author Allan Bowe
**/ **/
/** @cond */ /** @cond */
%macro mf_existfeature(feature %macro mf_existfeature(feature
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%let feature=%upcase(&feature); %let feature=%upcase(&feature);
@@ -27,11 +29,7 @@
%let platform=%mf_getplatform(); %let platform=%mf_getplatform();
%if &feature= %then %do; %if &feature= %then %do;
%put No feature was requested for detection; %put Supported features: PROCLUA;
%end;
%else %if &feature=COLCONSTRAINTS %then %do;
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;
%else 1;
%end; %end;
%else %if &feature=PROCLUA %then %do; %else %if &feature=PROCLUA %then %do;
/* https://blogs.sas.com/content/sasdummy/2015/08/03/using-lua-within-your-sas-programs */ /* https://blogs.sas.com/content/sasdummy/2015/08/03/using-lua-within-your-sas-programs */
@@ -40,20 +38,10 @@
%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;
/* is it possible to PROC EXPORT an excel file? */
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 1;
%else %if %sysfunc(sysprod(SAS/ACCESS Interface to PC Files)) = 1 %then 1;
%else 0;
%end;
%else %do; %else %do;
-1 -1
%put &sysmacroname: &feature not found; %put &sysmacroname: &feature not found;
%end; %end;
%mend mf_existfeature; %mend mf_existfeature;
/** @endcond */
/** @endcond */

View File

@@ -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 [in] fref the fileref to detect @param 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;

View File

@@ -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 function name @param [in] name (positional) - function name
@author Allan Bowe @author Allan Bowe
**/ **/

View File

@@ -25,17 +25,13 @@
%local dsid rc; %local dsid rc;
%let dsid=%sysfunc(open(&libds,is)); %let dsid=%sysfunc(open(&libds,is));
%if &dsid=0 %then %do; %if &dsid=0 or %length(&var)=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;

View File

@@ -6,8 +6,11 @@
%put %mf_existVarList(sashelp.class, age sex name dummyvar); %put %mf_existVarList(sashelp.class, age sex name dummyvar);
@param [in] libds 2 part dataset or view reference <h4> SAS Macros </h4>
@param [in] varlist space separated variable names @li mf_abort.sas
@param libds 2 part dataset or view reference
@param varlist space separated variable names
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe

View File

@@ -1,42 +0,0 @@
/**
@file
@brief Returns E8601DT26.6 if compatible else DATETIME19.3
@details From our experience in [Data Controller for SAS]
(https://datacontroller.io) deployments, the E8601DT26.6 datetime format has
the widest support when it comes to pass-through SQL queries.
However, it is not supported in WPS or early versions of SAS 9 (M3 and below)
when used as a datetime literal, eg:
data _null_;
demo="%sysfunc(datetime(),E8601DT26.6)"dt;
demo=;
run;
This macro will therefore return DATEITME19.3 as an alternative format
based on the runtime environment so that it can be used in such cases, eg:
data _null_;
demo="%sysfunc(datetime(),%mf_fmtdttm())"dt;
demo=;
run;
<h4> Related Macros </h4>
@li mf_fmtdttm.test.sas
@author Allan Bowe
**/
%macro mf_fmtdttm(
)/*/STORE SOURCE*/;
%if "&sysver"="9.2" or "&sysver"="9.3"
or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")
or "%substr(&sysver,1,1)"="4"
or "%substr(&sysver,1,1)"="5"
%then %do;DATETIME19.3%end;
%else %do;E8601DT26.6%end;
%mend mf_fmtdttm;

View File

@@ -31,7 +31,6 @@
%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
**/ **/

View File

@@ -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 [in] libds library.dataset @param libds library.dataset
@param [in] attr full list in [documentation]( @param 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.

View File

@@ -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 [in] libds library.dataset @param libds library.dataset
@param [in] attr Common values are NLOBS and NVARS, full list in [documentation]( @param 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.

View File

@@ -10,9 +10,10 @@
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.

View File

@@ -1,37 +0,0 @@
/**
@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;

View File

@@ -7,9 +7,8 @@
%put %mf_getkeyvalue(someindex) %put %mf_getkeyvalue(someindex)
@param [in] key Provide a key on which to perform the lookup @param key Provide a key on which to perform the lookup
@param [in] libds= (work.mp_setkeyvalue) The library.dataset which holds the @param libds= define the target table which holds the parameters
parameters
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe

View File

@@ -5,14 +5,10 @@
%put %mf_getplatform(); %put %mf_getplatform();
returns one of: returns:
SASMETA (or SASVIYA)
@li SASMETA @param switch the param for which to return a platform specific variable
@li SASVIYA
@li SASJS
@li BASESAS
@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
@@ -72,4 +68,4 @@
%else %if &switch=VIYARESTAPI %then %do; %else %if &switch=VIYARESTAPI %then %do;
%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/) %mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)
%end; %end;
%mend mf_getplatform; %mend mf_getplatform;

View File

@@ -8,7 +8,7 @@
returns: returns:
> dbo > dbo
@param [in] libref Library reference (also accepts a 2 level libds ref). @param 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

View File

@@ -28,17 +28,15 @@
be 8 characters, so a 7 letter prefix would mean `maxtries` should be 10. be 8 characters, so a 7 letter prefix would mean `maxtries` should be 10.
if using zero (0) as the prefix, a native assignment is used. if using zero (0) as the prefix, a native assignment is used.
@param [in] maxtries= (1000) the last part of the libref. Must be an integer. @param [in] maxtries= (1000) the last part of the libref. Must be an integer.
@param [in] lrecl= (32767) Provide a default lrecl with which to initialise
the generated fileref.
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
**/ **/
%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767); %macro mf_getuniquefileref(prefix=_,maxtries=1000);
%local rc fname; %local rc fname;
%if &prefix=0 %then %do; %if &prefix=0 %then %do;
%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl)); %let rc=%sysfunc(filename(fname,,temp));
%if &rc %then %put %sysfunc(sysmsg()); %if &rc %then %put %sysfunc(sysmsg());
&fname &fname
%end; %end;
@@ -49,7 +47,7 @@
%do x=0 %to &maxtries; %do x=0 %to &maxtries;
%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len); %let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);
%if %sysfunc(fileref(&fname)) > 0 %then %do; %if %sysfunc(fileref(&fname)) > 0 %then %do;
%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl)); %let rc=%sysfunc(filename(fname,,temp));
%if &rc %then %put %sysfunc(sysmsg()); %if &rc %then %put %sysfunc(sysmsg());
&fname &fname
%return; %return;

View File

@@ -3,55 +3,38 @@
@brief Returns an unused libref @brief Returns an unused libref
@details Use as follows: @details Use as follows:
libname mclib0 (work); libname mclib0 (work);
libname mclib1 (work); libname mclib1 (work);
libname mclib2 (work); libname mclib2 (work);
%let libref=%mf_getuniquelibref(); %let libref=%mf_getuniquelibref();
%put &=libref; %put &=libref;
which returns: which returns:
> mclib3 > mclib3
A blank value is returned if no usable libname is determined. @param prefix= first part of libref. Remember that librefs can only be 8 characters,
so a 7 letter prefix would mean that maxtries should be 10.
@param [in] prefix= (mclib) first part of the returned libref. As librefs can @param maxtries= the last part of the libref. Provide an integer value.
be as long as 8 characters, a maximum length of 7 characters is premitted
for this prefix.
@param [in] maxtries= (1000) Deprecated parameter. Remains here to ensure a
non-breaking change. Will be removed in v5.
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
**/ **/
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000); %macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
%local x; %local x libref;
%let x=0;
%if ( %length(&prefix) gt 7 ) %then %do; %do x=0 %to &maxtries;
%put %str(ERR)OR: The prefix parameter cannot exceed 7 characters.; %if %sysfunc(libref(&prefix&x)) ne 0 %then %do;
0 %let libref=&prefix&x;
%let rc=%sysfunc(libname(&libref,%sysfunc(pathname(work))));
%if &rc %then %put %sysfunc(sysmsg());
&prefix&x
%*put &sysmacroname: Libref &libref assigned as WORK and returned;
%return; %return;
%end; %end;
%else %if (%sysfunc(NVALID(&prefix,v7))=0) %then %do;
%put %str(ERR)OR: Invalid prefix (&prefix);
0
%return;
%end; %end;
%put unable to find available libref in range &prefix.0-&maxtries;
/* Set maxtries equal to '10 to the power of [# unused characters] - 1' */
%let maxtries=%eval(10**(8-%length(&prefix))-1);
%do x = 0 %to &maxtries;
%if %sysfunc(libref(&prefix&x)) ne 0 %then %do;
&prefix&x
%return;
%end;
%let x = %eval(&x + 1);
%end;
%put %str(ERR)OR: No usable libref in range &prefix.0-&maxtries;
%put %str(ERR)OR- Try reducing the prefix or deleting some libraries!;
0
%mend mf_getuniquelibref; %mend mf_getuniquelibref;

View File

@@ -10,7 +10,7 @@
> MCc59c750610321d4c8bf75faadbcd22 > MCc59c750610321d4c8bf75faadbcd22
@param [in] prefix= (MC) Sets a prefix for the new name @param prefix= set a prefix for the new name
@version 9.3 @version 9.3
@author Allan Bowe @author Allan Bowe

View File

@@ -13,6 +13,8 @@
%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)
@@ -21,19 +23,17 @@
@author Allan Bowe @author Allan Bowe
**/ **/
%macro mf_getuser( %macro mf_getuser(type=META
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local user; %local user metavar;
%if &type=OS %then %let metavar=_secureusername;
%else %let metavar=_metaperson;
%if %symexist(_sasjs_username) %then %let user=&_sasjs_username; %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER;
%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do; %else %if %symexist(&metavar) %then %do;
%let user=&SYS_COMPUTE_SESSION_OWNER; %if %length(&&&metavar)=0 %then %let user=&sysuserid;
%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 */ %else %let user=%scan(&&&metavar,1,@);
%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));
%end; %end;
%else %let user=&sysuserid; %else %let user=&sysuserid;

View File

@@ -13,9 +13,9 @@
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mp_setkeyvalue.sas @li mp_setkeyvalue.sas
@param [in] libds dataset to query @param libds dataset to query
@param [in] variable the variable which contains the value to return. @param variable the variable which contains the value to return.
@param [in] filter= (1) contents of where clause @param filter contents of where clause
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe

View File

@@ -2,48 +2,31 @@
@file @file
@brief Returns number of variables in a dataset @brief Returns number of variables in a dataset
@details Useful to identify those renagade datasets that have no columns! @details Useful to identify those renagade datasets that have no columns!
Can also be used to count for numeric, or character columns
%put Number of Variables=%mf_getvarcount(sashelp.class); %put Number of Variables=%mf_getvarcount(sashelp.class);
%put Character Variables=%mf_getvarcount(sashelp.class,typefilter=C);
%put Numeric Variables = %mf_getvarcount(sashelp.class,typefilter=N);
returns: returns:
> Number of Variables=4 > Number of Variables=4
@param libds Two part dataset (or view) reference.
@param [in] libds Two part dataset (or view) reference.
@param [in] typefilter= (A) Filter for certain types of column. Valid values:
@li A Count All columns
@li C Count Character columns only
@li N Count Numeric columns only
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
**/ **/
%macro mf_getvarcount(libds,typefilter=A %macro mf_getvarcount(libds
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local dsid nvars rc outcnt x; %local dsid nvars rc ;
%let dsid=%sysfunc(open(&libds)); %let dsid=%sysfunc(open(&libds));
%let nvars=.; %let nvars=.;
%let outcnt=0;
%let typefilter=%upcase(&typefilter);
%if &dsid %then %do; %if &dsid %then %do;
%let nvars=%sysfunc(attrn(&dsid,NVARS)); %let nvars=%sysfunc(attrn(&dsid,NVARS));
%if &typefilter=A %then %let outcnt=&nvars;
%else %if &nvars>0 %then %do x=1 %to &nvars;
/* increment based on variable type */
%if %sysfunc(vartype(&dsid,&x))=&typefilter %then %do;
%let outcnt=%eval(&outcnt+1);
%end;
%end;
%let rc=%sysfunc(close(&dsid)); %let rc=%sysfunc(close(&dsid));
%end; %end;
%else %do; %else %do;
%put unable to open &libds (rc=&dsid); %put unable to open &libds (rc=&dsid);
%let rc=%sysfunc(close(&dsid)); %let rc=%sysfunc(close(&dsid));
%end; %end;
&outcnt &nvars
%mend mf_getvarcount; %mend mf_getvarcount;

View File

@@ -25,8 +25,7 @@
@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 @param [in] force=(0) Set to 1 to supply a default if the variable has no format
format
@returns outputs format @returns outputs format
@author Allan Bowe @author Allan Bowe

View File

@@ -18,8 +18,8 @@
8 8
NOTE: Variable renegade does not exist in test NOTE: Variable renegade does not exist in test
@param [in] libds Two part dataset (or view) reference. @param libds Two part dataset (or view) reference.
@param [in] var Variable name for which a length should be returned @param var Variable name for which a length should be returned
@returns outputs length @returns outputs length
@author Allan Bowe @author Allan Bowe

View File

@@ -21,8 +21,8 @@ returns:
> NOTE: Variable renegade does not exist in test > NOTE: Variable renegade does not exist in test
@param [in] libds Two part dataset (or view) reference. @param libds Two part dataset (or view) reference.
@param [in] var Variable name for which a position should be returned @param var Variable name for which a position should be returned
@author Allan Bowe @author Allan Bowe
@version 9.2 @version 9.2

View File

@@ -13,8 +13,8 @@ Usage:
@param [in] libds Two part dataset (or view) reference. @param libds Two part dataset (or view) reference.
@param [in] var the variable name to be checked @param 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.

View File

@@ -10,7 +10,7 @@
returns: returns:
> TEMP > TEMP
@param [in] fref The fileref to check @param 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.

View File

@@ -1,29 +0,0 @@
/**
@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 &macro_name=%eval(&&&macro_name+&incr);
/* return the value */
&&&macro_name
%mend mf_increment;

View File

@@ -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 [in] Param VALUE to be checked @param param VALUE to be checked
@return output returns 1 (if blank) else 0 @return output returns 1 (if blank) else 0

View File

@@ -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 [in] path Full path of the file/directory to be checked @param 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

View File

@@ -20,11 +20,8 @@
%macro mf_isint(arg %macro mf_isint(arg
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
/* blank val is not an integer */
%if "&arg"="" %then %do;0%return;%end;
/* remove minus sign if exists */ /* remove minus sign if exists */
%local val; %local val;
%if "%substr(%str(&arg),1,1)"="-" %then %let val=%substr(%str(&arg),2); %if "%substr(%str(&arg),1,1)"="-" %then %let val=%substr(%str(&arg),2);
%else %let val=&arg; %else %let val=&arg;
@@ -33,4 +30,4 @@
%if %sysfunc(findc(%str(&val),,kd)) %then %do;0%end; %if %sysfunc(findc(%str(&val),,kd)) %then %do;0%end;
%else %do;1%end; %else %do;1%end;
%mend mf_isint; %mend mf_isint;

View File

@@ -6,10 +6,6 @@
%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
**/ **/
@@ -19,8 +15,7 @@
%local root; %local root;
%if &loc=POF or &loc=PLATFORMOBJECTFRAMEWORK %then %do; %if &loc=POF or &loc=PLATFORMOBJECTFRAMEWORK %then %do;
%let root=%sysget(SASROOT); %let root=%substr(%sysget(SASROOT),1,%index(%sysget(SASROOT),SASFoundation)-2);
%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

View File

@@ -7,7 +7,7 @@ Usage:
%mf_mkdir(/some/path/name) %mf_mkdir(/some/path/name)
@param [in] dir Relative or absolute pathname. Unquoted. @param dir relative or absolute pathname. Unquoted.
@version 9.2 @version 9.2
**/ **/

View File

@@ -8,8 +8,6 @@
%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
**/ **/

View File

@@ -9,7 +9,7 @@
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getattrn.sas @li mf_getattrn.sas
@param [in] libds library.dataset @param 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.

View File

@@ -1,63 +0,0 @@
/**
@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 */

View File

@@ -11,8 +11,8 @@
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@param [in] basestr The string to be modified @param basestr The string to be modified
@param [in] trimstr The string to be removed from the end of `basestr`, if it @param 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

View File

@@ -59,7 +59,7 @@
%goto exit_success; %goto exit_success;
%exit_err: %exit_err:
%put &abortmsg; %put %str(ERR)OR: &abortmsg;
%mf_abort(iftrue=(&mabort ne SOFT), %mf_abort(iftrue=(&mabort ne SOFT),
mac=mf_verifymacvars, mac=mf_verifymacvars,
msg=%str(&abortmsg) msg=%str(&abortmsg)

View File

@@ -13,8 +13,8 @@
returns: returns:
> blah blaaah brah > blah blaaah brah
@param [in] str1= () string containing words to extract @param str1= string containing words to extract
@param [in] str2= () used to compare with the extract string @param str2= used to compare with the extract string
@warning CASE SENSITIVE! @warning CASE SENSITIVE!

View File

@@ -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

View File

@@ -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

View File

@@ -8,34 +8,27 @@
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 WIN environments as this @li should not use endsas or abort cancel in 9.4m3 environments as this can
can cause hung multibridge sessions and result in a frozen STP server 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)
@li STP environments must finish cleanly to avoid the log being sent to @li STP environments must finish cleanly to avoid the log being sent to
_webout. To assist with this, we also run stpsrvset('program error', 0) _webout. To assist with this, we also run stpsrvset('program error', 0)
and set SYSCC=0. and set SYSCC=0. We take a unique "soft abort" approach - we open a macro
Where possible, we take a unique "soft abort" approach - we open a macro
but don't close it! This works everywhere EXCEPT inside a \%include inside but don't close it! This works everywhere EXCEPT inside a \%include inside
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 become ineffective in 9.4m6 WINDOWS environments. We are
currently investigating approaches to deal with this.
@param [in] mac= (mp_abort.sas) To contain the name of the calling macro. Do @param mac= to contain the name of the calling macro
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= supply a condition under 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).
@@ -46,18 +39,17 @@
@li msg (the message) @li msg (the message)
@li mac (the mac param) @li mac (the mac param)
@param [in] mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked @param mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked for
for an abort status. an abort status.
Valid values: Valid values:
@li REGULAR (default) @li REGULAR (default)
@li INCLUDE @li INCLUDE
@version 9.4
@author Allan Bowe
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mp_include.sas @li mp_include.sas
@version 9.4
@author Allan Bowe
@cond @cond
**/ **/
@@ -66,233 +58,167 @@
, mode=REGULAR , mode=REGULAR
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode; %global sysprocessmode sysprocessname;
%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 %superq(SYSPROCESSNAME) ne %str(Compute Server) and "&SYSPROCESSNAME " ne "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;
options nosyntaxcheck; %if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
%end; data &errds;
iftrue='1=1';
%if &mode=INCLUDE %then %do; length mac $100 msg $5000;
%if %sysfunc(exist(&errds))=1 %then %do; mac=symget('mac');
data _null_; msg=symget('msg');
set &errds; run;
call symputx('iftrue',iftrue,'l'); data _null_;
call symputx('mac',mac,'l'); abort cancel FILE;
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;
/* extract log errs / warns, if exist */ /* Stored Process Server web app context */
%local logloc logline; %if %symexist(_metaperson)
%global logmsg; /* capture global messages */ or "&SYSPROCESSNAME "="Compute Server "
%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG; or &mode=INCLUDE
%else %let logloc=%qsysfunc(getoption(LOG)); %then %do;
proc printto log=log;run; options obs=max replace nosyntaxcheck mprint;
%let logline=0; %if &mode=INCLUDE %then %do;
%if %length(&logloc)>0 %then %do; %if %sysfunc(exist(&errds))=1 %then %do;
data _null_; data _null_;
infile &logloc lrecl=5000; set &errds;
input; putlog _infile_; call symputx('iftrue',iftrue,'l');
i=1; call symputx('mac',mac,'l');
retain logonce 0; call symputx('msg',msg,'l');
if ( putlog (_all_)(=);
_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR" run;
) and logonce=0 then %if (&iftrue)=0 %then %return;
do; %end;
call symputx('logline',_n_); %else %do;
logonce+1; %put &sysmacroname: No include errors found;
end; %return;
run; %end;
/* capture log including lines BEFORE the err */ %end;
%if &logline>0 %then %do;
/* 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; input; putlog _infile_;
i=1; i=1;
stoploop=0; retain logonce 0;
if _n_ ge &logline-15 and stoploop=0 then do until (i>22); if (
call symputx('logmsg',catx('\n',symget('logmsg'),_infile_)); _infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
input; ) and logonce=0 then
i+1; do;
stoploop=1; call symputx('logline',_n_);
logonce+1;
end; end;
if stoploop=1 then stop;
run; run;
/* capture log including lines BEFORE the err */
%if &logline>0 %then %do;
data _null_;
infile &logloc lrecl=5000;
input;
i=1;
stoploop=0;
if _n_ ge &logline-15 and stoploop=0 then do until (i>22);
call symputx('logmsg',catx('\n',symget('logmsg'),_infile_));
input;
i+1;
stoploop=1;
end;
if stoploop=1 then stop;
run;
%end;
%end; %end;
%end;
%if %symexist(SYS_JES_JOB_URI) %then %do; %if %symexist(SYS_JES_JOB_URI) %then %do;
/* setup webout for Viya */ /* setup webout */
options nobomfile; OPTIONS NOBOMFILE;
%if "X&SYS_JES_JOB_URI.X"="XX" %then %do; %if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
filename _webout temp lrecl=999999 mod; 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; %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 */ /* 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_; data _null_;
putlog 'stpsrvset program err and syscc'; file _webout mod lrecl=32000 encoding='utf-8';
rc=stpsrvset('program error', 0); length msg $32767 ;
call symputx("syscc",0,"g"); sasdatetime=datetime();
run; msg=symget('msg');
%if &sysscp=WIN %if &logline>0 %then %do;
and 1=0 /* deprecating this logic until we figure out a consistent abort */ msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
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; %end;
%else %do; /* 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='""';
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 "}" @;
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;
/** /**
* endsas kills 9.4m3 deployments by orphaning multibridges. * endsas kills 9.4m3 deployments by orphaning multibridges.
* Abort variants are ungraceful (non zero return code) * Abort variants are ungraceful (non zero return code)
@@ -310,29 +236,28 @@ and %superq(SYSPROCESSNAME) ne %str(Compute Server)
run; run;
%inc skip; %inc skip;
%end; %end;
%end; %else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do; /* endsas kills the session making it harder to fetch results */
/* endsas kills the session making it harder to fetch results */ data _null_;
data _null_; syswarningtext=symget('syswarningtext');
syswarningtext=symget('syswarningtext'); syserrortext=symget('syserrortext');
syserrortext=symget('syserrortext'); abort_msg=symget('msg');
abort_msg=symget('msg'); syscc=symget('syscc');
syscc=symget('syscc'); sysuserid=symget('sysuserid');
sysuserid=symget('sysuserid'); iftrue=symget('iftrue');
iftrue=symget('iftrue'); put (_all_)(/=);
put (_all_)(/=); call symputx('syscc',0);
call symputx('syscc',0); abort cancel nolist;
abort cancel nolist; run;
run; %end;
%else %do;
%abort cancel;
%end;
%end; %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 */

View File

@@ -1,95 +0,0 @@
/**
@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;

View File

@@ -17,8 +17,8 @@
%mp_appendfile(baseref=tmp1, appendrefs=tmp2 tmp3) %mp_appendfile(baseref=tmp1, appendrefs=tmp2 tmp3)
@param [in] baseref= (0) Fileref of the base file (should exist) @param [in] baseref= Fileref of the base file (should exist)
@param [in] appendrefs= (0) One or more filerefs to be appended to the base @param [in] appendrefs= One or more filerefs to be appended to the base
fileref. Space separated. fileref. Space separated.
@version 9.2 @version 9.2

View File

@@ -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 'ERR' +(-1) "OR: " msg; putlog "%str(ERR)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 'ERR' +(-1) "OR: " msg; putlog "%str(ERR)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 'ERR' +(-1) "OR: " msg; putlog "%str(ERR)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 'ERR' +(-1) "OR: " msg; putlog "%str(ERR)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 'ERR' +(-1) "OR: " msg; putlog "%str(ERR)OR: " msg;
%end; %end;
output; output;
end; end;

View File

@@ -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|Dataset &inds contained ALL columns| |User Provided description|PASS|Column &inds contained ALL columns|
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe

View File

@@ -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= (0) The list of columns to check for @param [in] cols= The list of columns to check for
@param [in] desc= (0) The user provided test description @param [in] desc= (Testing observations) 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|Dataset &inds contained ALL columns| |User Provided description|PASS|Column &inds contained ALL columns|
<h4> Related Macros </h4> <h4> Related Macros </h4>

View File

@@ -36,13 +36,12 @@
@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= (0) A library.dataset.column value containing a UNIQUE @param [in] checkvals= 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|
@@ -98,7 +97,7 @@
%let test=%upcase(&test); %let test=%upcase(&test);
%if &test ne ALLVALS and &test ne ANYVAL and &test ne NOVAL %then %do; %if &test ne ALLVALS and &test ne ANYVAL %then %do;
%mp_abort( %mp_abort(
mac=&sysmacroname, mac=&sysmacroname,
msg=%str(Invalid test - &test) msg=%str(Invalid test - &test)
@@ -109,12 +108,12 @@
%let result=-1; %let result=-1;
%let orig=-1; %let orig=-1;
proc sql noprint; proc sql noprint;
select count(*) into: result trimmed select count(*) into: result
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 trimmed from &lib..&ds; select count(*) into: orig from &lib..&ds;
quit; quit;
%local notfound tmp1 tmp2; %local notfound tmp1 tmp2;
@@ -146,7 +145,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/&orig values " test_comments="&sysmacroname: &lib..&ds..&col has &result 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';
@@ -154,9 +153,6 @@
%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;

View File

@@ -74,8 +74,7 @@
outds=work.test_results outds=work.test_results
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local ds test_result test_comments del add mod ilist; %local ds test_result test_comments del add mod ilist;
%let ilist=%upcase(&sasjs_prefix._FUNCTIONS SYS_PROCHTTP_STATUS_CODE %let ilist=%upcase(&sasjs_prefix._FUNCTIONS &ignorelist);
SYS_PROCHTTP_STATUS_CODE SYS_PROCHTTP_STATUS_PHRASE &ignorelist);
/** /**
* this sets up the global vars, it will also enter STRICT mode. If this * this sets up the global vars, it will also enter STRICT mode. If this
@@ -90,7 +89,7 @@
create table &scopeds as create table &scopeds as
select name,offset,value select name,offset,value
from dictionary.macros from dictionary.macros
where scope="&scope" and upcase(name) not in (%mf_getquotedstr(&ilist)) where scope="&scope" and name not in (%mf_getquotedstr(&ilist))
order by name,offset; order by name,offset;
%end; %end;
%else %if &action=COMPARE %then %do; %else %if &action=COMPARE %then %do;
@@ -104,9 +103,7 @@
%let ds=&syslast; %let ds=&syslast;
proc compare proc compare base=&scopeds compare=&ds;
base=&scopeds(where=(upcase(name) not in (%mf_getquotedstr(&ilist))))
compare=&ds noprint;
run; run;
%if &sysinfo=0 %then %do; %if &sysinfo=0 %then %do;
@@ -144,4 +141,4 @@
drop table &ds; drop table &ds;
%end; %end;
%mend mp_assertscope; %mend mp_assertscope;

View File

@@ -28,8 +28,8 @@
put _infile_; put _infile_;
run; run;
@param [in] inref= (0) Fileref of the input file (should exist) @param [in] inref= Fileref of the input file (should exist)
@param [out] outref= (0) Output fileref. If it does not exist, it is created. @param [out] outref= 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

View File

@@ -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.
Note that if you have a new enough version of SAS, and you don't need features Based on:
such as APPEND, you may be better of using the fcopy() function instead. https://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout) %mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
@@ -22,17 +22,15 @@
%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 create @param [out] outloc quoted "path/and/filename.ext" of the file to be created
@param [in] inref= (____in) If provided, this fileref takes precedence over @param [in] inref (____in) If provided, this fileref will take precedence over
the `inloc` parameter the `inloc` parameter
@param [out] outref= (____in) If provided, this fileref takes precedence @param [out] outref (____in) If provided, this fileref will take 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
@@ -46,14 +44,15 @@
,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; %local mod outmode;
%if &mode=APPEND %then %do;
%if not(%eval(%unquote(&iftrue))) %then %return; %let mod=mod;
%let outmode='a';
%if &mode=APPEND %then %let mod=mod; %end;
%else %do;
%let outmode='o';
%end;
/* these IN and OUT filerefs can point to anything */ /* these IN and OUT filerefs can point to anything */
%if &inref = ____in %then %do; %if &inref = ____in %then %do;
filename &inref &inloc lrecl=1048576 ; filename &inref &inloc lrecl=1048576 ;
@@ -64,17 +63,22 @@
/* copy the file byte-for-byte */ /* copy the file byte-for-byte */
data _null_; data _null_;
infile &inref lrecl=1 recfm=n; length filein 8 fileid 8;
file &outref &mod recfm=n; filein = fopen("&inref",'I',1,'B');
input sourcechar $char1. @@; fileid = fopen("&outref",&outmode,1,'B');
format sourcechar hex2.; rec = '20'x;
put sourcechar char1. @@; do while(fread(filein)=0);
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;

View File

@@ -1,195 +0,0 @@
/**
@file
@brief Splits a file of ANY SIZE by reference to a search string.
@details Provide a fileref and a search string to chop off part of a file.
Works by reading in the file byte by byte, then marking the beginning and end
of each matched string, before finally doing the chop.
Choose whether to keep the FIRST or the LAST section of the file. Optionally,
use an OFFSET to fix the precise chop point.
Usage:
%let src="%sysfunc(pathname(work))/file.txt";
%let str=Chop here!;
%let out1="%sysfunc(pathname(work))/file1.txt";
%let out2="%sysfunc(pathname(work))/file2.txt";
%let out3="%sysfunc(pathname(work))/file3.txt";
%let out4="%sysfunc(pathname(work))/file4.txt";
data _null_;
file &src;
put "startsection&str.endsection";
run;
%mp_chop(&src, matchvar=str, keep=FIRST, outfile=&out1)
%mp_chop(&src, matchvar=str, keep=LAST, outfile=&out2)
%mp_chop(&src, matchvar=str, keep=FIRST, matchpoint=END, outfile=&out3)
%mp_chop(&src, matchvar=str, keep=LAST, matchpoint=END, outfile=&out4)
filename results (&out1 &out2 &out3 &out4);
data _null_;
infile results;
input;
list;
run;
Results:
@li `startsection`
@li `Chop here!endsection`
@li `startsectionChop here!`
@li `endsection`
For more examples, see mp_chop.test.sas
@param [in] infile The QUOTED path to the file on which to perform the chop
@param [in] matchvar= () Macro variable NAME containing the string to split by
@param [in] matchpoint= (START) Valid values:
@li START - chop at the beginning of the string in `matchvar`.
@li END - chop at the end of the string in `matchvar`.
@param [in] offset= (0) An adjustment to the precise chop location, by
by reference to the `matchpoint`. Should be a positive or negative integer.
@param [in] keep= (FIRST) Valid values:
@li FIRST - keep the section of the file before the chop
@li LAST - keep the section of the file after the chop
@param [in] mdebug= (0) Set to 1 to provide macro debugging
@param [out] outfile= (0)
Optional QUOTED path to the adjusted output file (avoids
overwriting the first file).
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
<h4> Related Macros </h4>
@li mp_abort.sas
@li mp_gsubfile.sas
@li mp_replace.sas
@li mp_chop.test.sas
@version 9.4
@author Allan Bowe
**/
%macro mp_chop(infile,
matchvar=,
matchpoint=START,
keep=FIRST,
offset=0,
mdebug=0,
outfile=0
)/*/STORE SOURCE*/;
%local fref0 dttm ds1 outref;
%let fref0=%mf_getuniquefileref();
%let ds1=%mf_getuniquename(prefix=allchars);
%let ds2=%mf_getuniquename(prefix=startmark);
%if &outfile=0 %then %let outfile=&infile;
%mp_abort(iftrue= (%length(%superq(&matchvar))=0)
,mac=mp_chop.sas
,msg=%str(&matchvar is an empty variable)
)
/* START */
%let dttm=%sysfunc(datetime());
filename &fref0 &infile lrecl=1 recfm=n;
/* create dataset with one char per row */
data &ds1;
infile &fref0;
input sourcechar $char1. @@;
format sourcechar hex2.;
run;
/* get start & stop position of first matchvar string (one row, two vars) */
data &ds2;
/* set find string to length in bytes to cover trailing spaces */
length string $ %length(%superq(&matchvar));
string =symget("&matchvar");
drop string;
firstchar=char(string,1);
findlen=lengthm(string); /* <- for trailing bytes */
do _N_=1 to nobs;
set &ds1 nobs=nobs point=_N_;
if sourcechar=firstchar then do;
pos=1;
s=0;
do point=_N_ to min(_N_ + findlen -1,nobs);
set &ds1 point=point;
if sourcechar=char(string, pos) then s + 1;
else goto _leave_;
pos+1;
end;
_leave_:
if s=findlen then do;
START =_N_;
_N_ =_N_+ s - 1;
STOP =_N_;
output;
/* matched! */
stop;
end;
end;
end;
stop;
keep START STOP;
run;
%local split;
%let split=0;
data _null_;
set &ds2;
if "&matchpoint"='START' then do;
if "&keep"='FIRST' then mp=start;
else if "&keep"='LAST' then mp=start-1;
end;
else if "&matchpoint"='END' then do;
if "&keep"='FIRST' then mp=stop+1;
else if "&keep"='LAST' then mp=stop;
end;
split=mp+&offset;
call symputx('split',split,'l');
%if &mdebug=1 %then %do;
put (_all_)(=);
%put &=offset;
%end;
run;
%if &split=0 %then %do;
%put &sysmacroname: No match found in &infile for string %superq(&matchvar);
%return;
%end;
data _null_;
file &outfile recfm=n;
set &ds1;
%if &keep=FIRST %then %do;
if _n_ ge &split then stop;
%end;
%else %do;
if _n_ gt &split;
%end;
put sourcechar char1.;
run;
%if &mdebug=0 %then %do;
filename &fref0 clear;
%end;
%else %do;
data _null_;
infile &outfile lrecl=32767;
input;
list;
if _n_>200 then stop;
run;
%end;
/* END */
%put &sysmacroname took %sysevalf(%sysfunc(datetime())-&dttm) seconds to run;
%mend mp_chop;

View File

@@ -1,5 +1,5 @@
/** /**
@file @file mp_cleancsv.sas
@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,17 +7,14 @@
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] in= (NOTPROVIDED) @param in= provide path or fileref to input csv
Provide path or fileref to input csv. If a period is @param out= output path or fileref to output csv
found, it is assumed to be a file. @param qchar= quote char - hex code 22 is the double quote.
@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
@@ -59,14 +56,9 @@
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='0D'x then do; if inchar ne '0A'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;

View File

@@ -25,7 +25,6 @@
<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
@@ -58,33 +57,25 @@
%end; %end;
proc format lib=&libcat cntlout=&cntlds; proc format lib=&libcat cntlout=&cntlds;
%if "&fmtlist" ne "0" and "&fmtlist" ne "" %then %do; %if "&fmtlist" ne "0" %then %do;
select select
%do i=1 %to %sysfunc(countw(&fmtlist,%str( ))); %do i=1 %to %sysfunc(countw(&fmtlist));
%scan(&fmtlist,&i,%str( )) %scan(&fmtlist,&i,%str( ))
%end; %end;
; ;
%end; %end;
run; run;
data &cntlout/nonote2err; data &cntlout;
if 0 then set &ddlds; if 0 then set &ddlds;
set &cntlds; set &cntlds;
by type fmtname notsorted; if type="N" then do;
start=cats(start);
/* align the numeric values to avoid overlapping ranges */ end=cats(end);
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=1;
else fmtrow+1;
run; run;
proc sort; proc sort;
by type fmtname fmtrow; by fmtname start;
run; run;
proc sql; proc sql;

View File

@@ -16,11 +16,8 @@
%mp_copyfolder(&rootdir,&copydir) %mp_copyfolder(&rootdir,&copydir)
@param [in] source Unquoted path to the folder to copy from. @param source Unquoted path to the folder to copy from.
@param [out] target Unquoted path to the folder to copy to. @param target Unquoted path to the folder to copy to.
@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
recursion, set to MAX.
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getuniquename.sas @li mf_getuniquename.sas
@@ -34,7 +31,7 @@
**/ **/
%macro mp_copyfolder(source,target,copymax=MAX); %macro mp_copyfolder(source,target);
%mp_abort(iftrue=(%mf_isdir(&source)=0) %mp_abort(iftrue=(%mf_isdir(&source)=0)
,mac=&sysmacroname ,mac=&sysmacroname
@@ -53,7 +50,7 @@
%let tempds=%mf_getuniquename(); %let tempds=%mf_getuniquename();
/* recursive directory listing */ /* recursive directory listing */
%mp_dirlist(path=&source,outds=work.&tempds,maxdepth=&copymax) %mp_dirlist(path=&source,outds=work.&tempds, maxdepth=MAX)
/* create folders and copy content */ /* create folders and copy content */
data _null_; data _null_;
@@ -69,7 +66,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 'ERR' +(-1) "OR: Unable to copy " filepath " to " filepath2; putlog "%str(ERR)OR: Unable to copy " filepath " to " filepath2;
putlog msg=; putlog msg=;
end; end;
end; end;
@@ -81,4 +78,4 @@
proc sql; proc sql;
drop table work.&tempds; drop table work.&tempds;
%mend mp_copyfolder; %mend mp_copyfolder;

View File

@@ -30,7 +30,6 @@
@li mddl_dc_filtersummary.sas @li mddl_dc_filtersummary.sas
@li mddl_dc_locktable.sas @li mddl_dc_locktable.sas
@li mddl_dc_maxkeytable.sas @li mddl_dc_maxkeytable.sas
@li mf_getuniquename.sas
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mp_filterstore.sas @li mp_filterstore.sas
@@ -47,7 +46,7 @@
%macro mp_coretable(table_ref,libds=0 %macro mp_coretable(table_ref,libds=0
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local outds ; %local outds ;
%let outds=%sysfunc(ifc(&libds=0,%mf_getuniquename(),&libds)); %let outds=%sysfunc(ifc(&libds=0,_data_,&libds));
proc sql; proc sql;
%if &table_ref=DIFFTABLE %then %do; %if &table_ref=DIFFTABLE %then %do;
%mddl_dc_difftable(libds=&outds) %mddl_dc_difftable(libds=&outds)
@@ -66,8 +65,7 @@ proc sql;
%end; %end;
%if &libds=0 %then %do; %if &libds=0 %then %do;
proc sql;
describe table &syslast; describe table &syslast;
drop table &syslast; drop table &syslast;
%end; %end;
%mend mp_coretable; %mend mp_coretable;

View File

@@ -18,14 +18,11 @@
%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 [in] inds= (work.mp_getconstraints) The input table containing the @param inds= The input table containing the constraint info
constraint info @param outds= a table containing the create statements (create_statement column)
@param [out] outds= (work.mp_createconstraints) A table containing the create @param execute= `YES|NO` - default is NO. To actually create, use YES.
statements (create_statement column)
@param [in] execute= (NO) To actually create, use YES.
<h4> Related Files </h4> <h4> SAS Macros </h4>
@li mp_getconstraints.sas
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
@@ -33,7 +30,7 @@
**/ **/
%macro mp_createconstraints(inds=mp_getconstraints %macro mp_createconstraints(inds=mp_getconstraints
,outds=work.mp_createconstraints ,outds=mp_createconstraints
,execute=NO ,execute=NO
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
@@ -67,4 +64,4 @@ data &outds;
output; output;
run; run;
%mend mp_createconstraints; %mend mp_createconstraints;

View File

@@ -1,13 +1,49 @@
/** /**
@file mp_createwebservice.sas @file mp_createwebservice.sas
@brief Create a web service in SAS 9, Viya or SASjs Server (legacy) @brief Create a web service in SAS 9 or Viya
@details This is actually a wrapper for mx_createwebservice.sas, remaining @details Creates a SASJS ready Stored Process in SAS 9 or Job Execution
for legacy purposes. For new apps, use mx_createwebservice.sas. Service in SAS Viya
Usage:
%* compile macros ;
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
%* write some code;
filename ft15f001 temp;
parmcards4;
%* fetch any data from frontend ;
%webout(FETCH)
data example1 example2;
set sashelp.class;
run;
%* send data back;
%webout(OPEN)
%webout(ARR,example1) * Array format, fast, suitable for large tables ;
%webout(OBJ,example2) * Object format, easier to work with ;
%webout(CLOSE)
;;;;
%mp_createwebservice(path=/Public/app/common,name=appInit,replace=YES)
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mx_createwebservice.sas @li mf_getplatform.sas
@li mm_createwebservice.sas
@li mv_createwebservice.sas
@param [in,out] path= The full folder path where the service will be created
@param [in,out] name= Service name. Avoid spaces.
@param [in] desc= The description of the service (optional)
@param [in] precode= Space separated list of filerefs, pointing to the code
that needs to be attached to the beginning of the service (optional)
@param [in] code= (ft15f001) Space seperated fileref(s) of the actual code to
be added
@param [in] replace= (YES) Select YES to replace any existing service in that
location
@version 9.2
@author Allan Bowe
**/ **/
@@ -17,16 +53,33 @@
,code=ft15f001 ,code=ft15f001
,desc=This service was created by the mp_createwebservice macro ,desc=This service was created by the mp_createwebservice macro
,replace=YES ,replace=YES
,mdebug=0
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%mx_createwebservice(path=&path %if &syscc ge 4 %then %do;
%put syscc=&syscc - &sysmacroname will not execute in this state;
%return;
%end;
%local platform; %let platform=%mf_getplatform();
%if &platform=SASVIYA %then %do;
%if "&path"="HOME" %then %let path=/Users/&sysuserid/My Folder;
%mv_createwebservice(path=&path
,name=&name ,name=&name
,precode=&precode
,code=&code ,code=&code
,precode=&precode
,desc=&desc ,desc=&desc
,replace=&replace ,replace=&replace
,mdebug=&mdebug
) )
%end;
%else %do;
%if "&path"="HOME" %then %let path=/User Folders/&sysuserid/My Folder;
%mm_createwebservice(path=&path
,name=&name
,code=&code
,precode=&precode
,desc=&desc
,replace=&replace
)
%end;
%mend mp_createwebservice; %mend mp_createwebservice;

View File

@@ -19,12 +19,11 @@
%mp_csv2ds(inref=mycsv,outds=myds,baseds=sashelp.class) %mp_csv2ds(inref=mycsv,outds=myds,baseds=sashelp.class)
@param [in] inref= (0) Fileref to the CSV @param inref= fileref to the CSV
@param [out] outds= (0) Output ds (lib.ds format) @param outds= output ds (lib.ds format)
@param [in] view= (NO) Set to YES or NO to determine whether the output @param view= Set to YES or NO to determine whether the output should be
should be a view or not. Default is NO (not a view). a view or not. Default is NO (not a view).
@param [in] baseds= (0) @param baseds= Template dataset on which to create the input statement.
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

View File

@@ -17,11 +17,9 @@
%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 [in] inds= (mp_getconstraints) @param inds= The input table containing the constraint info
The input table containing constraint info @param outds= a table containing the drop statements (drop_statement column)
@param [out] outds= (mp_deleteconstraints) @param execute= `YES|NO` - default is NO. To actually drop, use YES.
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

View File

@@ -15,7 +15,7 @@
%mp_deletefolder(&rootdir) %mp_deletefolder(&rootdir)
@param [in] folder Unquoted path to the folder to delete. @param path Unquoted path to the folder to delete.
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getuniquename.sas @li mf_getuniquename.sas

View File

@@ -1,52 +0,0 @@
/**
@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/).
![](https://user-images.githubusercontent.com/4420615/188278365-2987db97-0594-4a39-ac81-dbacdef5cdc8.png)
@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;

View File

@@ -27,9 +27,6 @@
@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
@@ -66,7 +63,6 @@
, 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*/;
@@ -85,7 +81,7 @@ data;run;
data &out_ds(compress=no data &out_ds(compress=no
keep=file_or_folder filepath filename ext msg directory level keep=file_or_folder filepath filename ext msg directory level
); );
length directory filepath $2000 fref fref2 $8 file_or_folder $6 filename $255 length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80
ext $20 msg $200 foption $16; ext $20 msg $200 foption $16;
if _n_=1 then call missing(of _all_); if _n_=1 then call missing(of _all_);
retain level &level; retain level &level;
@@ -98,12 +94,6 @@ data &out_ds(compress=no
%end; %end;
if rc = 0 then do; if rc = 0 then do;
did = dopen(fref); did = dopen(fref);
if did=0 then do;
putlog "NOTE: This directory is empty, or does not exist - &path";
msg=sysmsg();
put (_all_)(=);
stop;
end;
/* attribute is OS-dependent - could be "Directory" or "Directory Name" */ /* attribute is OS-dependent - could be "Directory" or "Directory Name" */
numopts=doptnum(did); numopts=doptnum(did);
do i=1 to numopts; do i=1 to numopts;
@@ -111,6 +101,12 @@ data &out_ds(compress=no
if foption=:'Directory' then i=numopts; if foption=:'Directory' then i=numopts;
end; end;
directory=dinfo(did,foption); directory=dinfo(did,foption);
if did=0 then do;
putlog "NOTE: This directory is empty - " directory;
msg=sysmsg();
put _all_;
stop;
end;
rc = filename(fref); rc = filename(fref);
end; end;
else do; else do;
@@ -148,15 +144,6 @@ 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;
@@ -164,7 +151,6 @@ run;
data &out_ds; data &out_ds;
set &out_ds; set &out_ds;
length infoname infoval $60 fref $8; length infoname infoval $60 fref $8;
if _n_=1 then call missing(fref);
rc=filename(fref,filepath); rc=filename(fref,filepath);
drop rc infoname fid i close fref; drop rc infoname fid i close fref;
if file_or_folder='file' then do; if file_or_folder='file' then do;
@@ -244,9 +230,6 @@ 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))");
@@ -259,4 +242,4 @@ run;
proc sql; proc sql;
drop table &out_ds; drop table &out_ds;
%mend mp_dirlist; %mend mp_dirlist;

View File

@@ -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 [in] libds= () input dataset @param libds input dataset
@param [in] var= (0) variable to get distinct values for @param var variable to get distinct values for
@param [out] outvar= (formatteed_value) variable to create. @param outvar variable to create. Default: `formatted_value`
@param [out] outds= (work.mp_distinctfmtvalues) dataset to create. @param outds dataset to create. Default: work.mp_distinctfmtvalues
@param [in] varlen= (2000) length of variable to create @param varlen length of variable to create (default 200)
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe

View File

@@ -11,7 +11,7 @@
Usage: Usage:
%mp_ds2cards(sashelp.class %mp_ds2cards(base_ds=sashelp.class
, tgt_ds=work.class , tgt_ds=work.class
, cards_file= "C:\temp\class.sas" , cards_file= "C:\temp\class.sas"
, showlog=NO , showlog=NO
@@ -23,7 +23,7 @@
- explicity setting a unix LF - explicity setting a unix LF
- constraints / indexes etc - constraints / indexes etc
@param [in] base_ds Should be two level - eg work.blah. This is the table @param [in] base_ds= Should be two level - eg work.blah. This is the table
that is converted to a cards file. that is converted to a cards file.
@param [in] tgt_ds= Table that the generated cards file would create. @param [in] tgt_ds= Table that the generated cards file would create.
Optional - if omitted, will be same as BASE_DS. Optional - if omitted, will be same as BASE_DS.
@@ -48,10 +48,9 @@
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
@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
@@ -220,8 +219,7 @@ data _null_;
put ' @file'; put ' @file';
put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset"; put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset";
put " @details Generated by %nrstr(%%)mp_ds2cards()"; put " @details Generated by %nrstr(%%)mp_ds2cards()";
put " Source: https://github.com/sasjs/core"; put " Available on github.com/sasjs/core";
put ' @cond ';
put '**/'; put '**/';
put "data &tgt_ds &indexes;"; put "data &tgt_ds &indexes;";
put "attrib "; put "attrib ";
@@ -254,7 +252,6 @@ 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;
@@ -267,7 +264,6 @@ data _null_;
if __lastobs then do; if __lastobs then do;
put ';;;;'; put ';;;;';
put 'run;'; put 'run;';
put '/** @endcond **/';
stop; stop;
end; end;
run; run;
@@ -287,5 +283,4 @@ quit;
%put NOTE-;%put NOTE-; %put NOTE-;%put NOTE-;
%put NOTE- %sysfunc(dequote(&cards_file.)); %put NOTE- %sysfunc(dequote(&cards_file.));
%put NOTE-;%put NOTE-; %put NOTE-;%put NOTE-;
%mend mp_ds2cards; %mend mp_ds2cards;
/** @endcond **/

View File

@@ -118,21 +118,13 @@ data _null_;
header = cats(coalescec(varlabel(dsid,i),varnm),dlm); header = cats(coalescec(varlabel(dsid,i),varnm),dlm);
%end; %end;
%else %if &headerformat=SASJS %then %do; %else %if &headerformat=SASJS %then %do;
vlen=varlen(dsid,i); if vartype(dsid,i)='C' then header=cats(varnm,':$char',varlen(dsid,i),'.');
if vartype(dsid,i)='C' then header=cats(varnm,':$char',vlen,'.');
else do; else do;
vfmt=coalescec(varfmt(dsid,i),'0'); vfmt=coalescec(varfmt(dsid,i),'0');
fmttype=mcf_getfmttype(vfmt); fmttype=mcf_getfmttype(vfmt);
if fmttype='DATE' then header=cats(varnm,':date9.'); if fmttype='DATE' then header=cats(varnm,':date9.');
else if fmttype='DATETIME' then header=cats(varnm,':E8601DT26.6'); else if fmttype='DATETIME' then header=cats(varnm,':E8601DT26.6');
else if fmttype='TIME' then header=cats(varnm,':TIME12.'); else if fmttype='TIME' then header=cats(varnm,':TIME12.');
/**
* there is not much point importing a short length numeric like this,
* eg with best4., as the resulting variable will still be stored as
* length 8. We need a length or format statement to ensure variable
* is creatd with the smaller length...
**/
else if vlen<8 then header=cats(varnm,':best',vlen,'.');
else header=cats(varnm,':best.'); else header=cats(varnm,':best.');
end; end;
%end; %end;
@@ -159,7 +151,6 @@ data _null_;
set &ds end=last; set &ds end=last;
%do i=1 %to &vcnt; %do i=1 %to &vcnt;
%let var=%scan(&varlist,&i); %let var=%scan(&varlist,&i);
%local vlen&i;
%if %mf_getvartype(&ds,&var)=C %then %do; %if %mf_getvartype(&ds,&var)=C %then %do;
%let dsv1=%mf_getuniquename(prefix=csvcol1_); %let dsv1=%mf_getuniquename(prefix=csvcol1_);
%let dsv2=%mf_getuniquename(prefix=csvcol2_); %let dsv2=%mf_getuniquename(prefix=csvcol2_);

View File

@@ -1,20 +1,8 @@
/** /**
@file @file
@brief Fetches DDL for a specific table @brief A wrapper for mp_getddl.sas
@details Uses mp_getddl under the hood @details In the next release, this will be the main version.
@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

View File

@@ -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

View File

@@ -57,11 +57,6 @@
%local vars; %local vars;
%let vars=%upcase(%mf_getvarlist(&libds)); %let vars=%upcase(%mf_getvarlist(&libds));
%if %trim(X&vars)=X %then %do;
%put &sysmacroname: Table &libds has no columns!!;
%return;
%end;
/* create the header row */ /* create the header row */
data _null_; data _null_;
file &outref; file &outref;
@@ -92,13 +87,12 @@ data _null_;
run; run;
%if %upcase(&showlog)=YES %then %do; %if %upcase(&showlog)=YES %then %do;
options ps=max lrecl=max; options ps=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;

View File

@@ -1,110 +0,0 @@
/**
@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;

View File

@@ -37,12 +37,10 @@
@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= (work.badrecords) The output table, which is a copy of the @param [out] outds= The output table, which is a copy of the &inds. table
&inds. table plus a REASON_CD column, containing only bad records. plus a REASON_CD column, containing only bad records. If bad records found,
If bad records are found, the SYSCC value will be set to 1008 the SYSCC value will be set to 1008 (general data problem). Downstream
(a general data problem). processes should check this table (and return code) before continuing.
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
@@ -86,53 +84,15 @@
/** /**
* Sanitise the values based on valid value lists, then strip out * Sanitise the values based on valid value lists, then strip out
* quotes, commas, periods and spaces. * quotes, commas, periods and spaces.
* Only numeric values should remain
*/ */
%local reason_cd nobs; %local reason_cd nobs;
%let nobs=0; %let nobs=0;
data &outds; 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 end=last; set &inds;
length reason_cd $4032 vtype vtype2 $1 vnum dsid 8 tmp $4000; length reason_cd $4032;
drop tmp;
/* quick check to ensure column exists */
if upcase(VARIABLE_NM) not in
(%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE)))
then do;
REASON_CD="Variable "!!cats(variable_nm)!!" not in &targetds";
putlog REASON_CD= VARIABLE_NM=;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
return;
end;
/* need to open the dataset to get the column type */
retain dsid;
if _n_=1 then dsid=open("&targetds","i");
if dsid>0 then do;
vnum=varnum(dsid,VARIABLE_NM);
if vnum<1 then do;
/* should not happen as was also tested for above */
REASON_CD=cats("Variable (",VARIABLE_NM,") not found in &targetds");
putlog REASON_CD= dsid=;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
goto endstep;
end;
/* now we can get the type */
else vtype=vartype(dsid,vnum);
end;
else do;
REASON_CD=cats("Could not open &targetds");
putlog REASON_CD= dsid=;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
stop;
end;
/* closed list checks */ /* closed list checks */
if GROUP_LOGIC not in ('AND','OR') then do; if GROUP_LOGIC not in ('AND','OR') then do;
@@ -156,8 +116,17 @@ data &outds;
call symputx('nobs',_n_,'l'); call symputx('nobs',_n_,'l');
output; output;
end; end;
if upcase(VARIABLE_NM) not in
(%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE)))
then do;
REASON_CD="Variable "!!cats(variable_nm)!!" not in &targetds";
putlog REASON_CD= VARIABLE_NM=;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
end;
if OPERATOR_NM not in if OPERATOR_NM not in
('=','>','<','<=','>=','NE','GE','LE','BETWEEN','IN','NOT IN','CONTAINS') ('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS','GE','LE')
then do; then do;
REASON_CD='Invalid OPERATOR_NM: '!!cats(OPERATOR_NM); REASON_CD='Invalid OPERATOR_NM: '!!cats(OPERATOR_NM);
putlog REASON_CD= OPERATOR_NM=; putlog REASON_CD= OPERATOR_NM=;
@@ -166,96 +135,19 @@ data &outds;
output; output;
end; end;
/* special missing logic */
if vtype='N' & OPERATOR_NM in ('=','>','<','<=','>=','NE','GE','LE') then do;
if cats(upcase(raw_value)) in (
'.','.A','.B','.C','.D','.E','.F','.G','.H','.I','.J','.K','.L','.M','.N'
'.N','.O','.P','.Q','.R','.S','.T','.U','.V','.W','.X','.Y','.Z','._'
)
then do;
/* valid numeric - exit data step loop */
return;
end;
else if subpad(upcase(raw_value),1,1) in (
'A','B','C','D','E','F','G','H','I','J','K','L','M','N'
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z','_'
)
then do;
/* check if the raw_value contains a valid variable NAME */
vnum=varnum(dsid,subpad(raw_value,1,32));
if vnum>0 then do;
/* now we can get the type */
vtype2=vartype(dsid,vnum);
/* check type matches */
if vtype2=vtype then do;
/* valid target var - exit loop */
return;
end;
else do;
REASON_CD=cats("Compared Type (",vtype2,") is not (",vtype,")");
putlog REASON_CD= dsid=;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
goto endstep;
end;
end;
end;
end;
/* special logic */ /* special logic */
if OPERATOR_NM in ('IN','NOT IN','BETWEEN') then do; if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ','');
if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ',','); else if OPERATOR_NM in ('IN','NOT IN') then do;
else do; if substr(raw_value,1,1) ne '('
if substr(raw_value,1,1) ne '(' or substr(cats(reverse(raw_value)),1,1) ne ')'
or substr(cats(reverse(raw_value)),1,1) ne ')' then do;
then do; REASON_CD='Missing start/end bracket in RAW_VALUE';
REASON_CD='Missing start/end bracket in RAW_VALUE'; putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ; call symputx('reason_cd',reason_cd,'l');
call symputx('reason_cd',reason_cd,'l'); call symputx('nobs',_n_,'l');
call symputx('nobs',_n_,'l'); output;
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;
if OPERATOR_NM ='BETWEEN' and subpad(upcase(tmp),1,1) in (
'A','B','C','D','E','F','G','H','I','J','K','L','M','N'
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z','_'
)
then do;
/* check if the raw_value contains a valid variable NAME */
/* is not valid syntax for IN or NOT IN */
vnum=varnum(dsid,subpad(tmp,1,32));
if vnum>0 then do;
/* now we can get the type */
vtype2=vartype(dsid,vnum);
/* check type matches */
if vtype2=vtype then do;
/* valid target var - exit loop */
return;
end;
else do;
REASON_CD=cats("Compared Type (",vtype2,") is not (",vtype,")");
putlog REASON_CD= dsid=;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
goto endstep;
end;
end;
end;
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;
@@ -272,42 +164,14 @@ data &outds;
/* output records that contain values other than digits and spaces */ /* output records that contain values other than digits and spaces */
if notdigit(compress(raw_value3,' '))>0 then do; if notdigit(compress(raw_value3,' '))>0 then do;
if vtype='C' and subpad(upcase(raw_value),1,1) in (
'A','B','C','D','E','F','G','H','I','J','K','L','M','N'
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z','_'
)
then do;
/* check if the raw_value contains a valid variable NAME */
vnum=varnum(dsid,subpad(raw_value,1,32));
if vnum>0 then do;
/* now we can get the type */
vtype2=vartype(dsid,vnum);
/* check type matches */
if vtype2=vtype then do;
/* valid target var - exit loop */
return;
end;
else do;
REASON_CD=cats("Compared Char Type (",vtype2,") is not (",vtype,")");
putlog REASON_CD= dsid=;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
goto endstep;
end;
end;
end;
putlog raw_value3= $hex32.; putlog raw_value3= $hex32.;
REASON_CD=cats('Invalid RAW_VALUE:',raw_value); REASON_CD=cats('Invalid RAW_VALUE:',raw_value);
putlog (_all_)(=); putlog REASON_CD= raw_value= raw_value1= raw_value2= raw_value3=;
call symputx('reason_cd',reason_cd,'l'); call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l'); call symputx('nobs',_n_,'l');
output; output;
end; end;
endstep:
if last then rc=close(dsid);
run; run;

View File

@@ -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= (filter) The output fileref to contain the filter clause. @param [out] outref= The output fileref to contain the filter clause. Will
Will be created (or replaced). be created (or replaced).
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mp_filtercheck.sas @li mp_filtercheck.sas
@@ -84,9 +84,6 @@ filename &outref temp;
run; run;
%end; %end;
%else %do; %else %do;
proc sort data=&inds;
by SUBGROUP_ID;
run;
data _null_; data _null_;
file &outref lrecl=32800; file &outref lrecl=32800;
set &inds end=last; set &inds end=last;

View File

@@ -44,10 +44,9 @@
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= (1) set to 1 to enable DEBUG messages @param [in] mdebug= set to 1 to enable DEBUG messages
@param [out] outresult= (work.result) The result table with the FILTER_RK @param [out] outresult= The result table with the FILTER_RK
@param [out] outquery= (work.query) The original query, taken as extract @param [out] outquery= The original query, taken as extract after table load
after table load
<h4> SAS Macros </h4> <h4> SAS Macros </h4>

View File

@@ -12,15 +12,14 @@
%mp_getcols(sashelp.airline,outds=work.myds) %mp_getcols(sashelp.airline,outds=work.myds)
@param [in] ds The dataset from which to obtain column metadata @param ds The dataset from which to obtain column metadata
@param [out] outds= (work.cols) The output dataset to create. Sample data: @param 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

View File

@@ -40,22 +40,6 @@
%let lib=%upcase(&lib); %let lib=%upcase(&lib);
%let ds=%upcase(&ds); %let ds=%upcase(&ds);
/**
* Cater for environments where sashelp.vcncolu is not available
*/
%if %sysfunc(exist(sashelp.vcncolu,view))=0 %then %do;
proc sql;
create table &outds(
libref char(8)
,TABLE_NAME char(32)
,constraint_type char(8) label='Constraint Type'
,constraint_name char(32) label='Constraint Name'
,column_name char(32) label='Column'
,constraint_order num
);
%return;
%end;
/** /**
* Neither dictionary tables nor sashelp provides a constraint order column, * Neither dictionary tables nor sashelp provides a constraint order column,
* however they DO arrive in the correct order. So, create the col. * however they DO arrive in the correct order. So, create the col.
@@ -94,11 +78,8 @@ 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 a.TABLE_CATALOG="&lib" where calculated libref="&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"

View File

@@ -23,10 +23,10 @@
@li mf_getquotedstr.sas @li mf_getquotedstr.sas
@li mp_getconstraints.sas @li mp_getconstraints.sas
@param [in] liblist= (SASHELP) Space seperated list of librefs to take as @param liblist= Space seperated list of librefs to take as
input input (Default=SASHELP)
@param [out] outref= (getdbml) Fileref to contain the DBML @param outref= Fileref to contain the DBML (Default=getdbml)
@param [in] showlog= (NO) set to YES to show the DBML in the log @param showlog= set to YES to show the DBML in the log (Default is NO)
@version 9.3 @version 9.3
@author Allan Bowe @author Allan Bowe

View File

@@ -22,21 +22,16 @@
@li mf_getvarcount.sas @li mf_getvarcount.sas
@li mp_getconstraints.sas @li mp_getconstraints.sas
@param [in] libref Libref of the library to create DDL for. Should already @param lib libref of the library to create DDL for. Should be assigned.
be assigned. @param ds dataset to create ddl for (optional)
@param [in] ds dataset to create ddl for (optional) @param fref= the fileref to which to _append_ the DDL. If it does not exist,
@param [in] fref= (getddl) the fileref to which to _append_ the DDL. If it it will be created.
does not exist, it will be created. @param flavour= The type of DDL to create (default=SAS). Supported=TSQL
@param [in] flavour= (SAS) The type of DDL to create. Options: @param showlog= Set to YES to show the DDL in the log
@li SAS @param schema= Choose a preferred schema name (default is to use actual schema
@li TSQL ,else libref)
@param applydttm= for non SAS DDL, choose if columns are created with native
@param [in]showlog= (NO) Set to YES to show the DDL in the log datetime2 format or regular decimal type
@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
**/ **/
@@ -135,13 +130,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;

View File

@@ -7,15 +7,10 @@
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
@@ -54,9 +49,7 @@ 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
@@ -73,7 +66,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);
@@ -97,7 +90,8 @@ 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 */
@@ -121,10 +115,6 @@ 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;

View File

@@ -41,7 +41,6 @@
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mcf_length.sas @li mcf_length.sas
@li mf_getuniquename.sas @li mf_getuniquename.sas
@li mf_getvarcount.sas
@li mf_getvarlist.sas @li mf_getvarlist.sas
@li mf_getvartype.sas @li mf_getvartype.sas
@li mf_getvarformat.sas @li mf_getvarformat.sas
@@ -61,7 +60,7 @@
,outds=work.mp_getmaxvarlengths ,outds=work.mp_getmaxvarlengths
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local vars prefix x var fmt srcds; %local vars prefix x var fmt;
%let vars=%mf_getvarlist(libds=&libds); %let vars=%mf_getvarlist(libds=&libds);
%let prefix=%substr(%mf_getuniquename(),1,25); %let prefix=%substr(%mf_getuniquename(),1,25);
%let num2char=%upcase(&num2char); %let num2char=%upcase(&num2char);
@@ -71,24 +70,6 @@
%mcf_length(wrap=YES, insert_cmplib=YES) %mcf_length(wrap=YES, insert_cmplib=YES)
%end; %end;
%if &num2char=NO
and ("%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5")
and %mf_getvarcount(&libds,typefilter=N) gt 0
%then %do;
/* custom functions not supported in summary operations */
%let srcds=%mf_getuniquename();
data &srcds/view=&srcds;
set &libds;
%do x=1 %to %sysfunc(countw(&vars,%str( )));
%let var=%scan(&vars,&x);
%if %mf_getvartype(&libds,&var)=N %then %do;
&prefix.&x=mcf_length(&var);
%end;
%end;
run;
%end;
%else %let srcds=&libds;
proc sql; proc sql;
create table &outds (rename=( create table &outds (rename=(
%do x=1 %to %sysfunc(countw(&vars,%str( ))); %do x=1 %to %sysfunc(countw(&vars,%str( )));
@@ -113,15 +94,10 @@ create table &outds (rename=(
%end; %end;
%end; %end;
%else %do; %else %do;
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then %do; max(mcf_length(&var)) as &prefix.&x
max(&prefix.&x) as &prefix.&x
%end;
%else %do;
max(mcf_length(&var)) as &prefix.&x
%end;
%end; %end;
%end; %end;
from &srcds; from &libds;
proc transpose data=&outds proc transpose data=&outds
out=&outds(rename=(_name_=NAME COL1=MAXLEN)); out=&outds(rename=(_name_=NAME COL1=MAXLEN));

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