1
0
mirror of https://github.com/sasjs/core.git synced 2025-12-11 14:34:35 +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
370 changed files with 8050 additions and 19097 deletions

View File

@@ -117,54 +117,8 @@
"contributions": [
"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,
"skipCi": true,
"commitType": "docs"
"skipCi": true
}

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
# 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
sasjs lint
# Avoid commits to the master branch
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`)
2. Make your change
3. Update the `all.sas` file (`python3 build.py`)
4. Commit using a [Conventional Commit](https://www.conventionalcommits.org)
5. Push and make a PR
4. 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
auth SHA256
script-security 2
keepalive 10 120
remote-cert-tls server
setenv FORWARD_COMPATIBLE 1
client
server-poll-timeout 4
nobind
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 443 tcp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
dev tun
dev-type tun
ns-cert-type server
setenv opt tls-version-min 1.0 or-highest
reneg-sec 604800
sndbuf 0
rcvbuf 0
# NOTE: LZO commands are pushed by the Access Server at connect time.
# NOTE: The below line doesn't disable LZO.
comp-lzo no
verb 3
setenv PUSH_PEER_INFO
# Keys
ca ca.crt
cert user.crt
key user.key
tls-auth tls.key 1
# Security
nobind
persist-key
persist-tun
verb 3

View File

@@ -13,54 +13,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- 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
uses: actions/checkout@v2
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v4
uses: cycjimmy/semantic-release-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GH_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:
matrix:
node-version: [lts/hydrogen]
node-version: [lts/fermium]
steps:
- uses: actions/checkout@v2
@@ -39,39 +39,46 @@ jobs:
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 wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-focal.list
sudo apt update
sudo apt install openvpn3=17~betaUb22042+jammy
sudo apt install openvpn3
- name: Start Open VPN 3
run: openvpn3 session-start --config .github/vpn/config.ovpn
- name: Install Doxygen
run: sudo apt-get install doxygen
- name: Install dependencies
run: npm ci
- name: Check code style (aborts if errors found)
run: npx @sasjs/cli lint
- name: Check code style
run: npm run lint
- 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
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
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
run: npx @sasjs/cli cbd -t server
- name: Build Project
run: npm run build
- name: Run all tests
run: npx @sasjs/cli test -t server
- name: Run SASjs tests
run: npm run test
env:
CI: true
CLIENT: ${{secrets.CLIENT}}
SECRET: ${{secrets.SECRET}}
SAS_USERNAME: ${{secrets.SAS_USERNAME}}
SAS_PASSWORD: ${{secrets.SAS_PASSWORD}}
SERVER_URL: ${{secrets.SERVER_URL}}
SERVER_TYPE: ${{secrets.SERVER_TYPE}}
ACCESS_TOKEN: ${{secrets.ACCESS_TOKEN}}
REFRESH_TOKEN: ${{secrets.REFRESH_TOKEN}}

5
.gitignore vendored
View File

@@ -10,7 +10,4 @@ sasjsresults/
mc_*
# ignore .env files as they can contain sasjs access tokens
*.env*
~
*.env*

View File

@@ -1,7 +1,7 @@
tasks:
- init: npm install -g npm
- command: npm i
- command: npm i -g @sasjs/cli
- init: |
nvm install --lts
npm i -g @sasjs/cli
image:
file: .gitpod.dockerfile
@@ -24,4 +24,4 @@ github:
# add a "Review in Gitpod" button to pull requests (defaults to false)
addBadge: 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/
.git-hooks/
.vscode/
main.dox
make_singlefile.sh
*.md
.all-contributorsrc

View File

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

View File

@@ -6,7 +6,5 @@
"editor.rulers": [
80
],
"files.trimTrailingWhitespace": true,
"sasjs-for-vscode.target": "docsonly",
"sasjs-for-vscode.isLocal": true
"files.trimTrailingWhitespace": 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]
[![Github Workflow][githubworkflow-image]][githubworkflow-url]
![npm](https://img.shields.io/npm/dt/@sasjs/core)
![Snyk Vulnerabilities for npm package](https://img.shields.io/snyk/vulnerabilities/npm/@sasjs/core)
[![License](https://img.shields.io/apm/l/atomic-design-ui.svg)](/LICENSE)
![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 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
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:
@@ -29,30 +31,62 @@ Documentation: https://core.sasjs.io
## Components
### BASE folder (All Platforms)
### BASE library (All Platforms)
- OS independent
- Works on all SAS Platforms
- No X command
- Prefixes: `mf_`, `mp_`
- Prefixes: _mf_, _mp_
### DDL folder (All Platforms)
### DDL library (All Platforms)
- OS independent
- Works on all SAS Platforms
- 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).
### FCMP folder (All Platforms)
#### FCMP library (All Platforms)
- 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.
### 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.
@@ -70,63 +104,15 @@ endsubmit;
run;
```
- 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_`
- Prefixes: _ml_
## 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:
```sas
%let repoloc=/your/path/core;
options insert=(sasautos="&repoloc/base");
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");
options insert=(sasautos="/your/path/macrocore/base");
options insert=(sasautos="/your/path/macrocore/meta");
```
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)
- _ms_ for macro procedures that will only work with [@sasjs/server](https://github.com/sasjs/server)
- _mv_ for macro procedures that will only work in Viya
- _mx_ for macros that 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
- unix style line endings (lf)
- 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
- All macros should be compatible with SAS versions from support level B and above (so currently 9.3 and later). If an earlier version is not supported, then the macro should say as such in the header documentation, and exit gracefully.
- It's [best to avoid](https://git.datacontroller.io/dc/dc/issues/50) special / non-ASCII characters for compatibility with the widest variety of SAS installations.
- 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`).
## 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:
* 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)
* `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.
@@ -232,23 +216,11 @@ If you find this library useful, please leave a [star](https://github.com/sasjs/
![](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 ✨
<!-- 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 -->
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 -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<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" 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" 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" 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" 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" 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" valign="top" width="14.28%"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt="Mihajlo Medjedovic"/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="#infra-medjedovicm" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
</tr>
<tr>
<td align="center" 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" 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" valign="top" width="14.28%"><a href="https://github.com/vznesh"><img src="https://avatars.githubusercontent.com/u/28916792?v=4?s=100" width="100px;" alt="Vignesh T."/><br /><sub><b>Vignesh T.</b></sub></a><br /><a href="https://github.com/sasjs/core/issues?q=author%3Avznesh" title="Bug reports">🐛</a></td>
<td align="center" 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>
<tr>
<td align="center"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Allan Bowe</b></sub></a><br /><a href="#business-allanbowe" title="Business development">💼</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Code">💻</a> <a href="#content-allanbowe" title="Content">🖋</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Documentation">📖</a> <a href="#infra-allanbowe" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-allanbowe" title="Maintenance">🚧</a> <a href="#mentoring-allanbowe" title="Mentoring">🧑‍🏫</a> <a href="#question-allanbowe" title="Answering Questions">💬</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3Aallanbowe" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/rafgag"><img src="https://avatars.githubusercontent.com/u/69139928?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rafgag</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=rafgag" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/tmoody"><img src="https://avatars.githubusercontent.com/u/79837106?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Trevor Moody</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=tmoody" title="Code">💻</a></td>
<td align="center"><a href="https://krishna-acondy.io/"><img src="https://avatars.githubusercontent.com/u/2980428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Krishna Acondy</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=krishna-acondy" title="Code">💻</a> <a href="#infra-krishna-acondy" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-krishna-acondy" title="Blogposts">📝</a> <a href="#content-krishna-acondy" title="Content">🖋</a> <a href="#ideas-krishna-acondy" title="Ideas, Planning, & Feedback">🤔</a> <a href="#video-krishna-acondy" title="Videos">📹</a></td>
<td align="center"><a href="https://github.com/saadjutt01"><img src="https://avatars.githubusercontent.com/u/8914650?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muhammad Saad </b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=saadjutt01" title="Code">💻</a> <a href="#ideas-saadjutt01" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://www.erudicat.com/"><img src="https://avatars.githubusercontent.com/u/25773492?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yury Shkoda</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=YuryShkoda" title="Code">💻</a> <a href="#infra-YuryShkoda" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#video-YuryShkoda" title="Videos">📹</a></td>
<td align="center"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="#infra-medjedovicm" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/kkchandok"><img src="https://avatars.githubusercontent.com/u/46090627?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kkchandok</b></sub></a><br /><a href="#ideas-kkchandok" title="Ideas, Planning, & Feedback">🤔</a></td>
<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"><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>
</tr>
</table>
<!-- markdownlint-restore -->

9418
all.sas

File diff suppressed because it is too large Load Diff

View File

@@ -3,11 +3,6 @@
@brief Abort, ungracefully
@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>
@li mp_abort.sas
@@ -17,7 +12,7 @@
**/
%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;
@@ -29,4 +24,4 @@
%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
case insensitive in this context!)
@param [in] libds library.dataset
@param libds library.dataset
@return output returns 1 or 0
<h4> Related Macros </h4>

View File

@@ -9,17 +9,19 @@
%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)
<h4> SAS Macros </h4>
@li mf_getplatform.sas
@version 8
@author Allan Bowe
**/
/** @cond */
%macro mf_existfeature(feature
)/*/STORE SOURCE*/;
%let feature=%upcase(&feature);
@@ -27,11 +29,7 @@
%let platform=%mf_getplatform();
%if &feature= %then %do;
%put No feature was requested for detection;
%end;
%else %if &feature=COLCONSTRAINTS %then %do;
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;
%else 1;
%put Supported features: PROCLUA;
%end;
%else %if &feature=PROCLUA %then %do;
/* 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 1;
%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;
-1
%put &sysmacroname: &feature not found;
%end;
%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.
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
that the fileref is found, but the file does not (yet) exist. If you need
@@ -30,4 +30,4 @@
0
%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
).
@param [in] name function name
@param [in] name (positional) - function name
@author Allan Bowe
**/

View File

@@ -25,17 +25,13 @@
%local dsid rc;
%let dsid=%sysfunc(open(&libds,is));
%if &dsid=0 %then %do;
%if &dsid=0 or %length(&var)=0 %then %do;
%put %sysfunc(sysmsg());
0
%end;
%else %if %length(&var)=0 %then %do;
0
%let rc=%sysfunc(close(&dsid));
0
%end;
%else %do;
%sysfunc(varnum(&dsid,&var))
%let rc=%sysfunc(close(&dsid));
%sysfunc(varnum(&dsid,&var))
%let rc=%sysfunc(close(&dsid));
%end;
%mend mf_existvar;

View File

@@ -6,8 +6,11 @@
%put %mf_existVarList(sashelp.class, age sex name dummyvar);
@param [in] libds 2 part dataset or view reference
@param [in] varlist space separated variable names
<h4> SAS Macros </h4>
@li mf_abort.sas
@param libds 2 part dataset or view reference
@param varlist space separated variable names
@version 9.2
@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/tests/jobs/somejob/);
@param [in] pgm The _program value from which to extract the appLoc
@author Allan Bowe
**/

View File

@@ -6,8 +6,8 @@
%put Dataset label = %mf_getattrc(sashelp.class,LABEL);
%put Member Type = %mf_getattrc(sashelp.class,MTYPE);
@param [in] libds library.dataset
@param [in] attr full list in [documentation](
@param libds library.dataset
@param attr full list in [documentation](
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
message if err.

View File

@@ -6,8 +6,8 @@
%put Number of observations=%mf_getattrn(sashelp.class,NLOBS);
%put Number of variables = %mf_getattrn(sashelp.class,NVARS);
@param [in] libds library.dataset
@param [in] attr Common values are NLOBS and NVARS, full list in [documentation](
@param libds library.dataset
@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)
@return output returns result of the attrn value supplied, or -1 and log
message if err.

View File

@@ -10,9 +10,10 @@
returns:
DOLLAR $CHAR W MONNAME
$CHAR BEST DOLLAR
BEST Z $CHAR COMMA PERCENTN
> DOLLAR $CHAR W MONNAME
> $CHAR BEST DOLLAR
> BEST Z $CHAR COMMA PERCENTN
@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)
@param [in] key Provide a key on which to perform the lookup
@param [in] libds= (work.mp_setkeyvalue) The library.dataset which holds the
parameters
@param key Provide a key on which to perform the lookup
@param libds= define the target table which holds the parameters
@version 9.2
@author Allan Bowe

View File

@@ -5,14 +5,10 @@
%put %mf_getplatform();
returns one of:
returns:
SASMETA (or SASVIYA)
@li SASMETA
@li SASVIYA
@li SASJS
@li BASESAS
@param [in] switch the param for which to return a platform specific variable
@param switch the param for which to return a platform specific variable
<h4> SAS Macros </h4>
@li mf_mval.sas
@@ -72,4 +68,4 @@
%else %if &switch=VIYARESTAPI %then %do;
%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)
%end;
%mend mf_getplatform;
%mend mf_getplatform;

View File

@@ -8,7 +8,7 @@
returns:
> 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

View File

@@ -28,17 +28,15 @@
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.
@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
@author Allan Bowe
**/
%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);
%macro mf_getuniquefileref(prefix=_,maxtries=1000);
%local rc fname;
%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());
&fname
%end;
@@ -49,7 +47,7 @@
%do x=0 %to &maxtries;
%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);
%if %sysfunc(fileref(&fname)) > 0 %then %do;
%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));
%let rc=%sysfunc(filename(fname,,temp));
%if &rc %then %put %sysfunc(sysmsg());
&fname
%return;

View File

@@ -3,55 +3,38 @@
@brief Returns an unused libref
@details Use as follows:
libname mclib0 (work);
libname mclib1 (work);
libname mclib2 (work);
libname mclib0 (work);
libname mclib1 (work);
libname mclib2 (work);
%let libref=%mf_getuniquelibref();
%put &=libref;
%let libref=%mf_getuniquelibref();
%put &=libref;
which returns:
> mclib3
A blank value is returned if no usable libname is determined.
@param [in] prefix= (mclib) first part of the returned libref. As librefs can
be as long as 8 characters, a maximum length of 7 characters is premitted
for this prefix.
@param [in] maxtries= (1000) Deprecated parameter. Remains here to ensure a
non-breaking change. Will be removed in v5.
@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 maxtries= the last part of the libref. Provide an integer value.
@version 9.2
@author Allan Bowe
**/
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
%local x;
%if ( %length(&prefix) gt 7 ) %then %do;
%put %str(ERR)OR: The prefix parameter cannot exceed 7 characters.;
0
%local x libref;
%let x=0;
%do x=0 %to &maxtries;
%if %sysfunc(libref(&prefix&x)) ne 0 %then %do;
%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;
%end;
%else %if (%sysfunc(NVALID(&prefix,v7))=0) %then %do;
%put %str(ERR)OR: Invalid prefix (&prefix);
0
%return;
%end;
/* 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
%put unable to find available libref in range &prefix.0-&maxtries;
%mend mf_getuniquelibref;

View File

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

View File

@@ -13,6 +13,8 @@
%let user= %mf_getUser();
%put &user;
@param type - do not use, may be deprecated in a future release
@return SYSUSERID (if workspace server)
@return _METAPERSON (if stored process server)
@return SYS_COMPUTE_SESSION_OWNER (if Viya compute session)
@@ -21,19 +23,17 @@
@author Allan Bowe
**/
%macro mf_getuser(
%macro mf_getuser(type=META
)/*/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;
%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;
%let user=&SYS_COMPUTE_SESSION_OWNER;
%end;
%else %if %symexist(_metaperson) %then %do;
%if %length(&_metaperson)=0 %then %let user=&sysuserid;
%if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER;
%else %if %symexist(&metavar) %then %do;
%if %length(&&&metavar)=0 %then %let user=&sysuserid;
/* sometimes SAS will add @domain extension - remove for consistency */
/* but be sure to quote in case of usernames with commas */
%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));
%else %let user=%scan(&&&metavar,1,@);
%end;
%else %let user=&sysuserid;

View File

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

View File

@@ -2,48 +2,31 @@
@file
@brief Returns number of variables in a dataset
@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 Character Variables=%mf_getvarcount(sashelp.class,typefilter=C);
%put Numeric Variables = %mf_getvarcount(sashelp.class,typefilter=N);
%put Number of Variables=%mf_getvarcount(sashelp.class);
returns:
> Number of Variables=4
@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
@param libds Two part dataset (or view) reference.
@version 9.2
@author Allan Bowe
**/
%macro mf_getvarcount(libds,typefilter=A
%macro mf_getvarcount(libds
)/*/STORE SOURCE*/;
%local dsid nvars rc outcnt x;
%local dsid nvars rc ;
%let dsid=%sysfunc(open(&libds));
%let nvars=.;
%let outcnt=0;
%let typefilter=%upcase(&typefilter);
%if &dsid %then %do;
%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));
%end;
%else %do;
%put unable to open &libds (rc=&dsid);
%let rc=%sysfunc(close(&dsid));
%end;
&outcnt
&nvars
%mend mf_getvarcount;

View File

@@ -25,8 +25,7 @@
@param [in] libds Two part dataset (or view) reference.
@param [in] var Variable name for which a format should be returned
@param [in] force= (0) Set to 1 to supply a default if the variable has no
format
@param [in] force=(0) Set to 1 to supply a default if the variable has no format
@returns outputs format
@author Allan Bowe

View File

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

View File

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

View File

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

View File

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

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:
%put %mf_isblank(&var);
%put mf_isblank(&var);
inspiration:
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

View File

@@ -9,7 +9,7 @@
With thanks and full credit to Andrea Defronzo -
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

View File

@@ -20,11 +20,8 @@
%macro mf_isint(arg
)/*/STORE SOURCE*/;
/* blank val is not an integer */
%if "&arg"="" %then %do;0%return;%end;
/* remove minus sign if exists */
%local val;
%if "%substr(%str(&arg),1,1)"="-" %then %let val=%substr(%str(&arg),2);
%else %let val=&arg;
@@ -33,4 +30,4 @@
%if %sysfunc(findc(%str(&val),,kd)) %then %do;0%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;
@param [in] loc The item to locate, eg:
@li PLAATFORMOBJECTFRAMEWORK (or POF)
@li VIYACONFG
@version 9.2
@author Allan Bowe
**/
@@ -19,8 +15,7 @@
%local root;
%if &loc=POF or &loc=PLATFORMOBJECTFRAMEWORK %then %do;
%let root=%sysget(SASROOT);
%let root=%substr(&root,1,%index(&root,SASFoundation)-2);
%let root=%substr(%sysget(SASROOT),1,%index(%sysget(SASROOT),SASFoundation)-2);
%let root=&root/SASPlatformObjectFramework/&sysver;
%put Batch tools located at: &root;
&root

View File

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

View File

@@ -8,8 +8,6 @@
%if %mf_mval(maynotexist)=itdid %then %do;
@param [in] var The macro variable NAME to return the (possible) value for
@version 9.2
@author Allan Bowe
**/

View File

@@ -9,7 +9,7 @@
<h4> SAS Macros </h4>
@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
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>
@param [in] basestr The string to be modified
@param [in] trimstr The string to be removed from the end of `basestr`, if it
@param basestr The string to be modified
@param trimstr The string to be removed from the end of `basestr`, if it
exists
@return output returns result with the value of `trimstr` removed from the end

View File

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

View File

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

View File

@@ -16,8 +16,8 @@
returns:
> sss bram boo
@param [in] str1= () String containing words to extract
@param [in] str2= () Used to compare with the extract string
@param [in] str1= string containing words to extract
@param [in] str2= used to compare with the extract string
@version 9.2
@author Allan Bowe

View File

@@ -21,8 +21,8 @@
@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 O OUTPUT mode, writes new records from the beginning of the file.
@param [in] l1= () First line
@param [in] l2= () Second line (etc through to l10)
@param [in] l1= First line
@param [in] l2= Second line (etc through to l10)
<h4> Related Macros </h4>
@li mf_writefile.test.sas

View File

@@ -8,34 +8,27 @@
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
can cause hung multibridge sessions and result in a frozen STP server
@li The use of endsas in 9.4m6+ windows environments for POST requests to the
STP server can result in an empty response body
@li should not use endsas or abort cancel in 9.4m3 environments as this can
cause hung multibridge sessions and result in a frozen STP server
@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
recognise this and fetch the log of the parent session instead)
@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)
and set SYSCC=0.
Where possible, we take a unique "soft abort" approach - we open a macro
and set SYSCC=0. We take a unique "soft abort" approach - we open a macro
but don't close it! This works everywhere EXCEPT inside a \%include inside
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,
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
not use &sysmacroname as this will always resolve to MP_ABORT.
@param [out] msg= message to be returned
@param [in] iftrue= (1=1) Condition under which the macro should be executed
@param [in] errds= (work.mp_abort_errds) There is no clean way to end a
process within a %include called within a macro. Furthermore, there is no
way to test if a macro is called within a %include. To handle this
particular scenario, the %include should be switched for the mp_include.sas
macro.
@param mac= to contain the name of the calling macro
@param msg= message to be returned
@param iftrue= supply a condition under which the macro should be executed.
@param errds= (work.mp_abort_errds) There is no clean way to end a process
within a %include called within a macro. Furthermore, there is no way to
test if a macro is called within a %include. To handle this particular
scenario, the %include should be switched for the mp_include.sas macro.
This provides an indicator that we are running a macro within a \%include
(`_SYSINCLUDEFILEDEVICE`) and allows us to provide a dataset with the abort
values (msg, mac).
@@ -46,18 +39,17 @@
@li msg (the message)
@li mac (the mac param)
@param [in] mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked
for an abort status.
@param mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked for
an abort status.
Valid values:
@li REGULAR (default)
@li INCLUDE
@version 9.4
@author Allan Bowe
<h4> Related Macros </h4>
@li mp_include.sas
@version 9.4
@author Allan Bowe
@cond
**/
@@ -66,233 +58,167 @@
, mode=REGULAR
)/*/STORE SOURCE*/;
%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;
%local fref fid i;
%global sysprocessmode sysprocessname;
%if not(%eval(%unquote(&iftrue))) %then %return;
%if not(%eval(%unquote(&iftrue))) %then %return;
%put NOTE: /// mp_abort macro executing //;
%if %length(&mac)>0 %then %put NOTE- called by &mac;
%put NOTE - &msg;
%put NOTE: /// mp_abort macro executing //;
%if %length(&mac)>0 %then %put NOTE- called by &mac;
%put NOTE - &msg;
%if %symexist(_SYSINCLUDEFILEDEVICE)
/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */
and %superq(SYSPROCESSNAME) ne %str(Compute Server)
%then %do;
%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
data &errds;
iftrue='1=1';
length mac $100 msg $5000;
mac=symget('mac');
msg=symget('msg');
run;
data _null_;
abort cancel FILE;
run;
%return;
%end;
%end;
/* Web App Context */
%if %symexist(_PROGRAM)
or %superq(SYSPROCESSNAME) = %str(Compute Server)
or &mode=INCLUDE
%then %do;
options obs=max replace mprint;
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"
%if %symexist(_SYSINCLUDEFILEDEVICE)
/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */
and "&SYSPROCESSNAME " ne "Compute Server "
%then %do;
options nosyntaxcheck;
%end;
%if &mode=INCLUDE %then %do;
%if %sysfunc(exist(&errds))=1 %then %do;
data _null_;
set &errds;
call symputx('iftrue',iftrue,'l');
call symputx('mac',mac,'l');
call symputx('msg',msg,'l');
putlog (_all_)(=);
%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;
%if (&iftrue)=0 %then %return;
%end;
%else %do;
%put &sysmacroname: No include errors found;
%return;
%end;
%end;
/* extract log errs / warns, if exist */
%local logloc logline;
%global logmsg; /* capture global messages */
%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;
%else %let logloc=%qsysfunc(getoption(LOG));
proc printto log=log;run;
%let logline=0;
%if %length(&logloc)>0 %then %do;
data _null_;
infile &logloc lrecl=5000;
input; putlog _infile_;
i=1;
retain logonce 0;
if (
_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
) and logonce=0 then
do;
call symputx('logline',_n_);
logonce+1;
end;
run;
/* capture log including lines BEFORE the err */
%if &logline>0 %then %do;
/* Stored Process Server web app context */
%if %symexist(_metaperson)
or "&SYSPROCESSNAME "="Compute Server "
or &mode=INCLUDE
%then %do;
options obs=max replace nosyntaxcheck mprint;
%if &mode=INCLUDE %then %do;
%if %sysfunc(exist(&errds))=1 %then %do;
data _null_;
set &errds;
call symputx('iftrue',iftrue,'l');
call symputx('mac',mac,'l');
call symputx('msg',msg,'l');
putlog (_all_)(=);
run;
%if (&iftrue)=0 %then %return;
%end;
%else %do;
%put &sysmacroname: No include errors found;
%return;
%end;
%end;
/* extract log errs / warns, if exist */
%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_;
infile &logloc lrecl=5000;
input;
input; putlog _infile_;
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;
retain logonce 0;
if (
_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
) and logonce=0 then
do;
call symputx('logline',_n_);
logonce+1;
end;
if stoploop=1 then stop;
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;
%if %symexist(SYS_JES_JOB_URI) %then %do;
/* setup webout for Viya */
options nobomfile;
%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
filename _webout temp lrecl=999999 mod;
%if %symexist(SYS_JES_JOB_URI) %then %do;
/* setup webout */
OPTIONS NOBOMFILE;
%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
filename _webout temp lrecl=999999 mod;
%end;
%else %do;
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"
name="_webout.json" lrecl=999999 mod;
%end;
%end;
%else %do;
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"
name="_webout.json" lrecl=999999 mod;
%end;
%end;
%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;
options nobomfile;
/* set up http header for SASjs Server */
%let fid=%sysfunc(fopen(&fref,A));
%if &fid=0 %then %do;
%put %str(ERR)OR: %sysfunc(sysmsg());
%return;
%end;
%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));
%let rc=%sysfunc(fwrite(&fid));
%let rc=%sysfunc(fclose(&fid));
%let rc=%sysfunc(filename(&fref));
%end;
/* send response in SASjs JSON format */
data _null_;
file _webout mod lrecl=32000 encoding='utf-8';
length msg syswarningtext syserrortext $32767 mode $10 ;
sasdatetime=datetime();
msg=symget('msg');
%if &logline>0 %then %do;
msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
%end;
/* escape the escapes */
msg=tranwrd(msg,'\','\\');
/* escape the quotes */
msg=tranwrd(msg,'"','\"');
/* ditch the CRLFs as chrome complains */
msg=compress(msg,,'kw');
/* quote without quoting the quotes (which are escaped instead) */
msg=cats('"',msg,'"');
if symexist('_debug') then debug=quote(trim(symget('_debug')));
else debug='""';
if symget('sasjsprocessmode')='Stored Program' then mode='SASJS';
if mode ne 'SASJS' then put '>>weboutBEGIN<<';
put '{"SYSDATE" : "' "&SYSDATE" '"';
put ',"SYSTIME" : "' "&SYSTIME" '"';
put ',"sasjsAbort" : [{';
put ' "MSG":' msg ;
put ' ,"MAC": "' "&mac" '"}]';
put ",""SYSUSERID"" : ""&sysuserid"" ";
put ',"_DEBUG":' debug ;
if symexist('_metauser') then do;
_METAUSER=quote(trim(symget('_METAUSER')));
put ",""_METAUSER"": " _METAUSER;
_METAPERSON=quote(trim(symget('_METAPERSON')));
put ',"_METAPERSON": ' _METAPERSON;
end;
if symexist('SYS_JES_JOB_URI') then do;
SYS_JES_JOB_URI=quote(trim(symget('SYS_JES_JOB_URI')));
put ',"SYS_JES_JOB_URI": ' SYS_JES_JOB_URI;
end;
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" ";
syserrortext=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;
/* send response in SASjs JSON format */
data _null_;
putlog 'stpsrvset program err and syscc';
rc=stpsrvset('program error', 0);
call symputx("syscc",0,"g");
run;
%if &sysscp=WIN
and 1=0 /* deprecating this logic until we figure out a consistent abort */
and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"
and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;
/* skip approach (below) does not work in windows m6+ envs */
endsas;
file _webout mod lrecl=32000 encoding='utf-8';
length msg $32767 ;
sasdatetime=datetime();
msg=symget('msg');
%if &logline>0 %then %do;
msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
%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.
* Abort variants are ungraceful (non zero return code)
@@ -310,29 +236,28 @@ and %superq(SYSPROCESSNAME) ne %str(Compute Server)
run;
%inc skip;
%end;
%end;
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
/* endsas kills the session making it harder to fetch results */
data _null_;
syswarningtext=symget('syswarningtext');
syserrortext=symget('syserrortext');
abort_msg=symget('msg');
syscc=symget('syscc');
sysuserid=symget('sysuserid');
iftrue=symget('iftrue');
put (_all_)(/=);
call symputx('syscc',0);
abort cancel nolist;
run;
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
/* endsas kills the session making it harder to fetch results */
data _null_;
syswarningtext=symget('syswarningtext');
syserrortext=symget('syserrortext');
abort_msg=symget('msg');
syscc=symget('syscc');
sysuserid=symget('sysuserid');
iftrue=symget('iftrue');
put (_all_)(/=);
call symputx('syscc',0);
abort cancel nolist;
run;
%end;
%else %do;
%abort cancel;
%end;
%end;
%else %do;
%put _all_;
%abort cancel;
%end;
%end;
%else %do;
%put _all_;
%abort cancel;
%end;
%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)
@param [in] baseref= (0) Fileref of the base file (should exist)
@param [in] appendrefs= (0) One or more filerefs to be appended to the base
@param [in] baseref= Fileref of the base file (should exist)
@param [in] appendrefs= One or more filerefs to be appended to the base
fileref. Space separated.
@version 9.2

View File

@@ -94,7 +94,7 @@ data &outds;
if libref(lib) ne 0 then do;
msg=catx(' ','libref',lib,'is not assigned!');
%if &errds=0 %then %do;
putlog 'ERR' +(-1) "OR: " msg;
putlog "%str(ERR)OR: " msg;
%end;
output;
return;
@@ -102,7 +102,7 @@ data &outds;
if exist(cats(lib,'.',ds)) ne 1 then do;
msg=catx(' ','libds',lib,'.',ds,'does not exist!');
%if &errds=0 %then %do;
putlog 'ERR' +(-1) "OR: " msg;
putlog "%str(ERR)OR: " msg;
%end;
output;
return;
@@ -111,7 +111,7 @@ data &outds;
if is_fmt=0 then do;
msg=catx(' ','format',fmt,'on libds',lib,'.',ds,'.',var,'is not valid!');
%if &errds=0 %then %do;
putlog 'ERR' +(-1) "OR: " msg;
putlog "%str(ERR)OR: " msg;
%end;
output;
return;
@@ -123,7 +123,7 @@ data &outds;
if dsid=0 then do;
msg=catx(' ','libds',lib,'.',ds,' could not be opened!');
%if &errds=0 %then %do;
putlog 'ERR' +(-1) "OR: " msg;
putlog "%str(ERR)OR: " msg;
%end;
output;
return;
@@ -131,7 +131,7 @@ data &outds;
if varnum(dsid,var)<1 then do;
msg=catx(' ','Variable',lib,'.',ds,'.',var,' was not found!');
%if &errds=0 %then %do;
putlog 'ERR' +(-1) "OR: " msg;
putlog "%str(ERR)OR: " msg;
%end;
output;
end;

View File

@@ -22,7 +22,7 @@
results. If it does not exist, it will be created, with the following format:
|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
@author Allan Bowe

View File

@@ -31,8 +31,8 @@
@param [in] inds The input library.dataset to test for values
@param [in] cols= (0) The list of columns to check for
@param [in] desc= (0) The user provided test description
@param [in] cols= The list of columns to check for
@param [in] desc= (Testing observations) The user provided test description
@param [in] test= (ALL) The test to apply. Valid values are:
@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
@@ -41,7 +41,7 @@
results. If it does not exist, it will be created, with the following format:
|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>

View File

@@ -36,13 +36,12 @@
@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).
@param [in] desc= (Testing observations) The user provided test description
@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 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
results. If it does not exist, it will be created, with the following format:
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
@@ -98,7 +97,7 @@
%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(
mac=&sysmacroname,
msg=%str(Invalid test - &test)
@@ -109,12 +108,12 @@
%let result=-1;
%let orig=-1;
proc sql noprint;
select count(*) into: result trimmed
select count(*) into: result
from &lib..&ds
where &col not in (
select &ccol from &clib..&cds
);
select count(*) into: orig trimmed from &lib..&ds;
select count(*) into: orig from &lib..&ds;
quit;
%local notfound tmp1 tmp2;
@@ -146,7 +145,7 @@
length test_description $256 test_result $4 test_comments $256;
test_description=symget('desc');
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');
%if &test=ANYVAL %then %do;
if &result < &orig then test_result='PASS';
@@ -154,9 +153,6 @@
%else %if &test=ALLVALS %then %do;
if &result=0 then test_result='PASS';
%end;
%else %if &test=NOVAL %then %do;
if &result=&orig then test_result='PASS';
%end;
%else %do;
test_comments="&sysmacroname: Unsatisfied test condition - &test";
%end;

View File

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

View File

@@ -28,8 +28,8 @@
put _infile_;
run;
@param [in] inref= (0) Fileref of the input file (should exist)
@param [out] outref= (0) Output fileref. If it does not exist, it is created.
@param [in] inref= Fileref of the input file (should exist)
@param [out] outref= Output fileref. If it does not exist, it is created.
@param [in] action= (ENCODE) The action to take. Valid values:
@li ENCODE - Convert the file to base64 format
@li DECODE - Decode the file from base64 format

View File

@@ -4,8 +4,8 @@
@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
default filerefs can be modified.
Note that if you have a new enough version of SAS, and you don't need features
such as APPEND, you may be better of using the fcopy() function instead.
Based on:
https://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
@@ -22,17 +22,15 @@
%mp_binarycopy(inref=tmp1, outref=tmp2, mode=APPEND)
@param [in] inloc= () quoted "path/and/filename.ext" of the file to be copied
@param [out] outloc= () quoted "path/and/filename.ext" of the file to create
@param [in] inref= (____in) If provided, this fileref takes precedence over
@param [in] inloc quoted "path/and/filename.ext" of the file to be copied
@param [out] outloc quoted "path/and/filename.ext" of the file to be created
@param [in] inref (____in) If provided, this fileref will take precedence over
the `inloc` parameter
@param [out] outref= (____in) If provided, this fileref takes precedence
@param [out] outref (____in) If provided, this fileref will take precedence
over the `outloc` parameter. It must already exist!
@param [in] mode= (CREATE) Valid values:
@param [in] mode (CREATE) Valid values:
@li CREATE - Create the file (even if it already exists)
@li APPEND - Append to the file (don't overwrite)
@param [in] iftrue= (1=1)
Supply a condition for which the macro should be executed
@returns nothing
@@ -46,14 +44,15 @@
,inref=____in /* override default to use own filerefs */
,outref=____out /* override default to use own filerefs */
,mode=CREATE
,iftrue=%str(1=1)
)/*/STORE SOURCE*/;
%local mod;
%if not(%eval(%unquote(&iftrue))) %then %return;
%if &mode=APPEND %then %let mod=mod;
%local mod outmode;
%if &mode=APPEND %then %do;
%let mod=mod;
%let outmode='a';
%end;
%else %do;
%let outmode='o';
%end;
/* these IN and OUT filerefs can point to anything */
%if &inref = ____in %then %do;
filename &inref &inloc lrecl=1048576 ;
@@ -64,17 +63,22 @@
/* copy the file byte-for-byte */
data _null_;
infile &inref lrecl=1 recfm=n;
file &outref &mod recfm=n;
input sourcechar $char1. @@;
format sourcechar hex2.;
put sourcechar char1. @@;
length filein 8 fileid 8;
filein = fopen("&inref",'I',1,'B');
fileid = fopen("&outref",&outmode,1,'B');
rec = '20'x;
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;
%if &inref = ____in %then %do;
filename &inref clear;
%end;
%if &outref=____out %then %do;
filename &outref clear;
%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
@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
@@ -7,17 +7,14 @@
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.
Usage:
usage:
fileref mycsv "/path/your/csv";
%mp_cleancsv(in=mycsv,out=/path/new.csv)
@param [in] in= (NOTPROVIDED)
Provide path or fileref to input csv. If a period is
found, it is assumed to be a file.
@param [in] out= (NOTPROVIDED) Output path or fileref to output csv.
If a period is found, it is assumed to be a file.
@param [in] qchar= ('22'x) Quote char - hex code 22 is the double quote.
@param in= provide path or fileref to input csv
@param out= output path or fileref to output csv
@param qchar= quote char - hex code 22 is the double quote.
@version 9.2
@author Allan Bowe
@@ -59,14 +56,9 @@
else do;
/* outside a quote, change cr and lf to crlf */
if inchar='0D'x then do;
crblank:
put '0D0A'x;
input inchar $char1.;
if inchar='0D'x then do;
/* multiple CR indicates CR formatted file with blank lines */
goto crblank;
end;
else if inchar ne '0A'x then do;
if inchar ne '0A'x then do;
put inchar $char1.;
if inchar=qchar then isq = mod(isq+1,2);
end;

View File

@@ -25,7 +25,6 @@
<h4> SAS Macros </h4>
@li mddl_sas_cntlout.sas
@li mf_getuniquename.sas
@li mp_aligndecimal.sas
<h4> Related Macros </h4>
@li mf_getvarformat.sas
@@ -58,33 +57,25 @@
%end;
proc format lib=&libcat cntlout=&cntlds;
%if "&fmtlist" ne "0" and "&fmtlist" ne "" %then %do;
%if "&fmtlist" ne "0" %then %do;
select
%do i=1 %to %sysfunc(countw(&fmtlist,%str( )));
%do i=1 %to %sysfunc(countw(&fmtlist));
%scan(&fmtlist,&i,%str( ))
%end;
;
%end;
run;
data &cntlout/nonote2err;
data &cntlout;
if 0 then set &ddlds;
set &cntlds;
by type fmtname notsorted;
/* align the numeric values to avoid overlapping ranges */
if type in ("I","N") then do;
%mp_aligndecimal(start,width=16)
%mp_aligndecimal(end,width=16)
if type="N" then do;
start=cats(start);
end=cats(end);
end;
/* create row marker. Data cannot be sorted without it! */
if first.fmtname then fmtrow=1;
else fmtrow+1;
run;
proc sort;
by type fmtname fmtrow;
by fmtname start;
run;
proc sql;

View File

@@ -16,11 +16,8 @@
%mp_copyfolder(&rootdir,&copydir)
@param [in] source Unquoted path to the folder to copy from.
@param [out] target Unquoted path to the folder to copy to.
@param [in] copymax= (MAX) Set to a positive integer to indicate the level of
subdirectory copy recursion - eg 3, to go `./3/levels/deep`. For unlimited
recursion, set to MAX.
@param source Unquoted path to the folder to copy from.
@param target Unquoted path to the folder to copy to.
<h4> SAS Macros </h4>
@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)
,mac=&sysmacroname
@@ -53,7 +50,7 @@
%let tempds=%mf_getuniquename();
/* 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 */
data _null_;
@@ -69,7 +66,7 @@
rc2=filename(fref2,filepath2,'disk','recfm=n');
if fcopy(fref1,fref2) ne 0 then do;
msg=sysmsg();
putlog 'ERR' +(-1) "OR: Unable to copy " filepath " to " filepath2;
putlog "%str(ERR)OR: Unable to copy " filepath " to " filepath2;
putlog msg=;
end;
end;
@@ -81,4 +78,4 @@
proc sql;
drop table work.&tempds;
%mend mp_copyfolder;
%mend mp_copyfolder;

View File

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

View File

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

View File

@@ -1,13 +1,49 @@
/**
@file mp_createwebservice.sas
@brief Create a web service in SAS 9, Viya or SASjs Server (legacy)
@details This is actually a wrapper for mx_createwebservice.sas, remaining
for legacy purposes. For new apps, use mx_createwebservice.sas.
@brief Create a web service in SAS 9 or Viya
@details Creates a SASJS ready Stored Process in SAS 9 or Job Execution
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>
@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
,desc=This service was created by the mp_createwebservice macro
,replace=YES
,mdebug=0
)/*/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
,precode=&precode
,code=&code
,precode=&precode
,desc=&desc
,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;

View File

@@ -19,12 +19,11 @@
%mp_csv2ds(inref=mycsv,outds=myds,baseds=sashelp.class)
@param [in] inref= (0) Fileref to the CSV
@param [out] outds= (0) Output ds (lib.ds format)
@param [in] view= (NO) Set to YES or NO to determine whether the output
should be a view or not. Default is NO (not a view).
@param [in] baseds= (0)
Template dataset on which to create the input statement.
@param inref= fileref to the CSV
@param outds= output ds (lib.ds format)
@param view= Set to YES or NO to determine whether the output should be
a view or not. Default is NO (not a view).
@param baseds= Template dataset on which to create the input statement.
Is used to determine types, lengths, and any informats.
@version 9.2

View File

@@ -17,11 +17,9 @@
%mp_getconstraints(lib=work,ds=example,outds=work.constraints)
%mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES)
@param [in] inds= (mp_getconstraints)
The input table containing constraint info
@param [out] outds= (mp_deleteconstraints)
Table containing the drop statements (drop_statement column)
@param [in] execute= (NO) `YES|NO` - default is NO. To actually drop, use YES.
@param inds= The input table containing the constraint info
@param outds= a table containing the drop statements (drop_statement column)
@param execute= `YES|NO` - default is NO. To actually drop, use YES.
@version 9.2

View File

@@ -15,7 +15,7 @@
%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>
@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
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
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] getattrs= (NO) If getattrs=YES then the doptname / foptname
functions are used to scan all properties - any characters that are not
@@ -66,7 +63,6 @@
, fref=0
, outds=work.mp_dirlist
, getattrs=NO
, showparent=NO
, maxdepth=0
, level=0 /* The level of recursion to perform. For internal use only. */
)/*/STORE SOURCE*/;
@@ -85,7 +81,7 @@ data;run;
data &out_ds(compress=no
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;
if _n_=1 then call missing(of _all_);
retain level &level;
@@ -98,12 +94,6 @@ data &out_ds(compress=no
%end;
if rc = 0 then do;
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" */
numopts=doptnum(did);
do i=1 to numopts;
@@ -111,6 +101,12 @@ data &out_ds(compress=no
if foption=:'Directory' then i=numopts;
end;
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);
end;
else do;
@@ -148,15 +144,6 @@ data &out_ds(compress=no
output;
end;
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;
run;
@@ -164,7 +151,6 @@ run;
data &out_ds;
set &out_ds;
length infoname infoval $60 fref $8;
if _n_=1 then call missing(fref);
rc=filename(fref,filepath);
drop rc infoname fid i close fref;
if file_or_folder='file' then do;
@@ -244,9 +230,6 @@ run;
data _null_;
set &out_ds;
where file_or_folder='folder';
%if &showparent=YES and &level=0 %then %do;
if filepath ne directory;
%end;
length code $10000;
code=cats('%nrstr(%mp_dirlist(path=',filepath,",outds=&outds"
,",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");
@@ -259,4 +242,4 @@ run;
proc sql;
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)
@param [in] libds= () input dataset
@param [in] var= (0) variable to get distinct values for
@param [out] outvar= (formatteed_value) variable to create.
@param [out] outds= (work.mp_distinctfmtvalues) dataset to create.
@param [in] varlen= (2000) length of variable to create
@param libds input dataset
@param var variable to get distinct values for
@param outvar variable to create. Default: `formatted_value`
@param outds dataset to create. Default: work.mp_distinctfmtvalues
@param varlen length of variable to create (default 200)
@version 9.2
@author Allan Bowe

View File

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

View File

@@ -118,21 +118,13 @@ data _null_;
header = cats(coalescec(varlabel(dsid,i),varnm),dlm);
%end;
%else %if &headerformat=SASJS %then %do;
vlen=varlen(dsid,i);
if vartype(dsid,i)='C' then header=cats(varnm,':$char',vlen,'.');
if vartype(dsid,i)='C' then header=cats(varnm,':$char',varlen(dsid,i),'.');
else do;
vfmt=coalescec(varfmt(dsid,i),'0');
fmttype=mcf_getfmttype(vfmt);
if fmttype='DATE' then header=cats(varnm,':date9.');
else if fmttype='DATETIME' then header=cats(varnm,':E8601DT26.6');
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.');
end;
%end;
@@ -159,7 +151,6 @@ data _null_;
set &ds end=last;
%do i=1 %to &vcnt;
%let var=%scan(&varlist,&i);
%local vlen&i;
%if %mf_getvartype(&ds,&var)=C %then %do;
%let dsv1=%mf_getuniquename(prefix=csvcol1_);
%let dsv2=%mf_getuniquename(prefix=csvcol2_);

View File

@@ -1,20 +1,8 @@
/**
@file
@brief Fetches DDL for a specific table
@details Uses mp_getddl under the hood
@brief A wrapper for mp_getddl.sas
@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>
@li mp_getddl.sas

View File

@@ -17,7 +17,7 @@
<h4> SAS Macros </h4>
@li mf_existds.sas
<h4> Related Macros </h4>
<h4> Related Macros <h4>
@li mp_jsonout.sas
@version 9.2

View File

@@ -57,11 +57,6 @@
%local vars;
%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 */
data _null_;
file &outref;
@@ -92,13 +87,12 @@ data _null_;
run;
%if %upcase(&showlog)=YES %then %do;
options ps=max lrecl=max;
options ps=max;
data _null_;
infile &outref;
if _n_=1 then putlog "# &libds" /;
input;
putlog _infile_;
run;
%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.
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] outds= (work.badrecords) The output table, which is a copy of the
&inds. table plus a REASON_CD column, containing only bad records.
If bad records are found, the SYSCC value will be set to 1008
(a general data problem).
Downstream processes should check this table (and return code) before
continuing.
@param [out] outds= The output table, which is a copy of the &inds. table
plus a REASON_CD column, containing only bad records. If bad records found,
the SYSCC value will be set to 1008 (general data problem). Downstream
processes should check this table (and return code) before continuing.
<h4> SAS Macros </h4>
@li mp_abort.sas
@@ -86,53 +84,15 @@
/**
* Sanitise the values based on valid value lists, then strip out
* quotes, commas, periods and spaces.
* Only numeric values should remain
*/
%local reason_cd nobs;
%let nobs=0;
data &outds;
/*length GROUP_LOGIC SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32
OPERATOR_NM $10 RAW_VALUE $4000;*/
set &inds end=last;
length reason_cd $4032 vtype vtype2 $1 vnum dsid 8 tmp $4000;
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;
set &inds;
length reason_cd $4032;
/* closed list checks */
if GROUP_LOGIC not in ('AND','OR') then do;
@@ -156,8 +116,17 @@ data &outds;
call symputx('nobs',_n_,'l');
output;
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
('=','>','<','<=','>=','NE','GE','LE','BETWEEN','IN','NOT IN','CONTAINS')
('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS','GE','LE')
then do;
REASON_CD='Invalid OPERATOR_NM: '!!cats(OPERATOR_NM);
putlog REASON_CD= OPERATOR_NM=;
@@ -166,96 +135,19 @@ data &outds;
output;
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 */
if OPERATOR_NM in ('IN','NOT IN','BETWEEN') then do;
if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ',',');
else do;
if substr(raw_value,1,1) ne '('
or substr(cats(reverse(raw_value)),1,1) ne ')'
then do;
REASON_CD='Missing start/end bracket in RAW_VALUE';
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
end;
else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));
end;
/* we now have a comma seperated list of values */
if vtype='N' then do i=1 to countc(raw_value1, ',')+1;
tmp=scan(raw_value1,i,',');
if cats(tmp) ne '.' and input(tmp, ?? 8.) eq . then do;
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;
if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ','');
else if OPERATOR_NM in ('IN','NOT IN') then do;
if substr(raw_value,1,1) ne '('
or substr(cats(reverse(raw_value)),1,1) ne ')'
then do;
REASON_CD='Missing start/end bracket in RAW_VALUE';
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
end;
else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));
end;
else raw_value1=raw_value;
@@ -272,42 +164,14 @@ data &outds;
/* output records that contain values other than digits and spaces */
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.;
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('nobs',_n_,'l');
output;
end;
endstep:
if last then rc=close(dsid);
run;

View File

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

View File

@@ -44,10 +44,9 @@
mp_coretable.sas as follows: `mp_coretable(LOCKTABLE)`.
@param [in] maxkeytable= (0) Optional permanent reference table used for
retained key tracking. Described in mp_retainedkey.sas.
@param [in] mdebug= (1) set to 1 to enable DEBUG messages
@param [out] outresult= (work.result) The result table with the FILTER_RK
@param [out] outquery= (work.query) The original query, taken as extract
after table load
@param [in] mdebug= set to 1 to enable DEBUG messages
@param [out] outresult= The result table with the FILTER_RK
@param [out] outquery= The original query, taken as extract after table load
<h4> SAS Macros </h4>

View File

@@ -12,15 +12,14 @@
%mp_getcols(sashelp.airline,outds=work.myds)
@param [in] ds The dataset from which to obtain column metadata
@param [out] outds= (work.cols) The output dataset to create. Sample data:
@param ds The dataset from which to obtain column metadata
@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.|
|---|---|---|---|---|---|---|---|
|`AIR `|`8 `|`2 `|`international airline travel (thousands) `|` `|`8. `|`N `|`NUMERIC `|
|`DATE `|`8 `|`1 `|`DATE `|`MONYY `|`MONYY. `|`N `|`DATE `|
|`REGION `|`3 `|`3 `|`REGION `|` `|`$3. `|`C `|`CHARACTER `|
<h4> Related Macros </h4>
@li mf_getvarlist.sas
@li mm_getcols.sas

View File

@@ -40,22 +40,6 @@
%let lib=%upcase(&lib);
%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,
* 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:
* 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;
and upcase(a.TABLE_NAME)="&ds"
and upcase(b.TABLE_NAME)="&ds"

View File

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

View File

@@ -22,21 +22,16 @@
@li mf_getvarcount.sas
@li mp_getconstraints.sas
@param [in] libref Libref of the library to create DDL for. Should already
be assigned.
@param [in] ds dataset to create ddl for (optional)
@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
@param lib libref of the library to create DDL for. Should be assigned.
@param ds dataset to create ddl for (optional)
@param fref= the fileref to which to _append_ the DDL. If it does not exist,
it will be created.
@param flavour= The type of DDL to create (default=SAS). Supported=TSQL
@param showlog= Set to YES to show the DDL in the log
@param schema= Choose a preferred schema name (default is to use actual schema
,else libref)
@param applydttm= for non SAS DDL, choose if columns are created with native
datetime2 format or regular decimal type
@version 9.3
@author Allan Bowe
**/
@@ -135,13 +130,13 @@ run;
%local x curds;
%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));
%let curds=%scan(&dsnlist,&x);
data _null_;
file &fref mod;
put "/* SAS Flavour DDL for %upcase(&libref).&curds */";
put "proc sql;";
run;
data _null_;
file &fref mod;
length lab $1024 typ $20;

View File

@@ -7,15 +7,10 @@
Formats are taken from the library / dataset reference and / or a static
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:
%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.
@todo Enable exporting of formats for an entire library
@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>
@li mf_getfmtlist.sas
@li mp_applyformats.sas
@li mp_cntlout.sas
@li mp_getformats.test.sas
@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;
%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 */
%let fmt=%scan(&fmtlist,&i,%str( ));
%let fmt=%mf_getfmtname(&fmt);
@@ -97,7 +90,8 @@ https://support.sas.com/documentation/cdl/en/proc/61895/HTML/default/viewer.htm#
proc sql;
create table &outsummary as
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;
/* ensure base table always exists */
@@ -121,10 +115,6 @@ create table &outsummary as
data &tempds;
if 0 then set &outdetail;
set &tempds;
/* set fmtrow (position of record within the format) */
by type fmtname notsorted;
if first.fmtname then fmtrow=1;
else fmtrow+1;
run;
proc append base=&outdetail data=&tempds ;
run;

View File

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

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