mirror of
https://github.com/sasjs/core.git
synced 2025-12-11 06:24:35 +00:00
Compare commits
669 Commits
v3.7.3
...
triggerstp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0102f7df0 | ||
|
|
aad1486c73 | ||
|
|
21d6671a5f | ||
|
|
5367126428 | ||
|
|
8485d9ebdf | ||
|
|
b7718fae6b | ||
|
|
6e3b100170 | ||
|
|
414fe9ebde | ||
|
|
2bdd83b2e5 | ||
|
|
862b1896fe | ||
|
|
22f0cb67a5 | ||
|
|
e6da373853 | ||
|
|
ed20bcaa5c | ||
|
|
96e8b096c5 | ||
|
|
7413266a8e | ||
|
|
cf70c33bde | ||
|
|
934629d46d | ||
|
|
16a3b63161 | ||
|
|
d7288b7fa1 | ||
|
|
015749a9b2 | ||
|
|
556c7bdb28 | ||
|
|
602758c3c3 | ||
|
|
a244a0b27b | ||
|
|
3bb632d60d | ||
|
|
bdd348483c | ||
|
|
92f575551d | ||
|
|
e616bc940f | ||
|
|
b7bca48129 | ||
|
|
6a2dcbb23f | ||
|
|
6da578e336 | ||
|
|
c874b31b63 | ||
|
|
532e0d535a | ||
|
|
ee5688f97f | ||
|
|
359b007f85 | ||
|
|
3294767c1b | ||
|
|
9d6f87c87a | ||
|
|
ec14b9cef8 | ||
|
|
94af8661b0 | ||
|
|
c9e431142c | ||
|
|
2b2aa5eb58 | ||
|
|
1ac2b480a6 | ||
|
|
4e53544b66 | ||
|
|
9b5f1cf170 | ||
|
|
703fe4ef38 | ||
|
|
f4a4263046 | ||
|
|
02bf9c85db | ||
|
|
5835cfaa83 | ||
|
|
b50521a8de | ||
|
|
fccd6fcc44 | ||
|
|
487ff5faa9 | ||
|
|
5efc20eacc | ||
|
|
cbd62fbfab | ||
|
|
2808145302 | ||
|
|
815e5f3e0e | ||
|
|
843d6e5c2d | ||
|
|
b084f4e84b | ||
|
|
5b5116070e | ||
|
|
a2002db838 | ||
|
|
dc6bcdd69e | ||
|
|
c97dc9a16d | ||
|
|
ef669db622 | ||
|
|
26499d2058 | ||
|
|
17e5d0f0e0 | ||
|
|
fc9205e355 | ||
|
|
ce344fc8e2 | ||
|
|
40239c53d8 | ||
|
|
814ecec94f | ||
|
|
934c501fec | ||
|
|
091b2e28be | ||
|
|
d8ea29bf8c | ||
|
|
993dec4610 | ||
|
|
f905387d66 | ||
|
|
7512423b04 | ||
|
|
50e6d416a4 | ||
|
|
18b6cadce6 | ||
|
|
413743bbe6 | ||
|
|
fcafb1026e | ||
|
|
b8f24264d4 | ||
|
|
5eb87a754e | ||
|
|
3a5fd4bfc5 | ||
|
|
b7ae9a2737 | ||
|
|
4057ac4b2e | ||
|
|
fa0a6ab22d | ||
|
|
2ae7a60be5 | ||
|
|
0a24f3ff7b | ||
|
|
592f477063 | ||
|
|
a91db81894 | ||
|
|
236e7cc4c0 | ||
|
|
2b6882cb9c | ||
|
|
2a3071708a | ||
|
|
3890aefccf | ||
|
|
f378a5637f | ||
|
|
121c692e09 | ||
|
|
759d6bd144 | ||
|
|
76d248f302 | ||
|
|
f30e30c024 | ||
|
|
30637b5025 | ||
|
|
9f16d090f5 | ||
|
|
74143bdf29 | ||
|
|
1c4c9793f6 | ||
|
|
d42fd4ebac | ||
|
|
d39b1be7a8 | ||
|
|
65d4c7969d | ||
|
|
fa152cb375 | ||
|
|
91a2d9039b | ||
|
|
73f919ffe7 | ||
|
|
68c11334df | ||
|
|
3bb83be0c5 | ||
|
|
76a20838ec | ||
|
|
9eec2e4920 | ||
|
|
bd18d4c32d | ||
|
|
d7763e276f | ||
|
|
7dadcf20f4 | ||
|
|
a497976eae | ||
|
|
6c64de651d | ||
|
|
48c17beb20 | ||
|
|
c46bb92c39 | ||
|
|
1e894bae98 | ||
|
|
461cda45ee | ||
|
|
7b6d34028b | ||
|
|
cb05ee2b9a | ||
|
|
e41b91f495 | ||
|
|
d21958cf0b | ||
|
|
c4b445db77 | ||
|
|
ebe764a7c0 | ||
|
|
7bba51a60e | ||
|
|
bce810caa0 | ||
|
|
222161d589 | ||
|
|
70cac82d78 | ||
|
|
6e0b8ae13b | ||
|
|
0dc4bbab62 | ||
|
|
da5244cda9 | ||
|
|
724de80d0f | ||
|
|
8de2dd4e7c | ||
|
|
e46165c140 | ||
|
|
a9185a2bf2 | ||
|
|
f0b77dfc6a | ||
|
|
91c4b87496 | ||
|
|
111d0dffc3 | ||
|
|
4f481ec8b4 | ||
|
|
b8cec22a88 | ||
|
|
6b1accdd6b | ||
|
|
949b406c23 | ||
|
|
fc90a7f928 | ||
|
|
d0bd88907f | ||
| 4e21772207 | |||
|
|
39f700bed2 | ||
|
|
dcb7958950 | ||
|
|
146610b5a7 | ||
|
|
9887efcf60 | ||
|
|
a2c7bdafb4 | ||
|
|
c58b5c7a52 | ||
|
|
22c7e5b4dd | ||
|
|
244171f8c4 | ||
|
|
fd765e2d68 | ||
| 840cb5ef44 | |||
| 918ce96fce | |||
| f1712c34e8 | |||
| 11ec20b472 | |||
| f42f111462 | |||
| 907725c5ba | |||
| 95b78b91e1 | |||
| e4771b9c14 | |||
| ba8190883e | |||
|
|
32dd057e83 | ||
|
|
7471bd42a4 | ||
|
|
702a4ecd3a | ||
|
|
5da97295ff | ||
|
|
dc556bdef0 | ||
|
|
111731bf35 | ||
|
|
2c526cf9dd | ||
|
|
660e02193f | ||
|
|
00b4dee86e | ||
|
|
3913825c22 | ||
|
|
0f143d603b | ||
|
|
f1d5fa2c0a | ||
|
|
a88689428f | ||
|
|
8843fa8bfc | ||
|
|
22d046cf5c | ||
|
|
29e3eb34aa | ||
|
|
1af52a6683 | ||
|
|
fc0c96dd94 | ||
|
|
b9c4882553 | ||
|
|
011b2b185c | ||
|
|
dbc23550ac | ||
|
|
8910840ccc | ||
|
|
4ef571032d | ||
|
|
e01cd8cd16 | ||
|
|
00628ec78a | ||
|
|
f4e6a487f3 | ||
|
|
b7afecdf81 | ||
|
|
19eb348f0e | ||
|
|
f420ac2abf | ||
|
|
7edec1ad8a | ||
|
|
62d7bce249 | ||
|
|
fe6c9a793b | ||
|
|
8e13943356 | ||
|
|
04df9600e0 | ||
|
|
e2b0aabfa4 | ||
|
|
c52a623630 | ||
|
|
cf348e8016 | ||
|
|
6502fc4982 | ||
|
|
ef574f6319 | ||
|
|
5b251006cd | ||
|
|
b353acec47 | ||
|
|
8b148c3916 | ||
|
|
2efdcec54c | ||
|
|
f832e93f4b | ||
|
|
f37c2e5867 | ||
|
|
6f8ec5d5a8 | ||
|
|
6521ade608 | ||
|
|
2666bbc85e | ||
|
|
ee35f47f4f | ||
|
|
7f867e2a5c | ||
|
|
c6af6ce578 | ||
|
|
a1aac785c0 | ||
|
|
dbe8b0b1c3 | ||
|
|
2ee9a4cee4 | ||
|
|
3a7afdffb7 | ||
|
|
c78211aa1c | ||
|
|
76c49e96f2 | ||
|
|
984ea44f5d | ||
|
|
88f1222abd | ||
|
|
d88f028ee3 | ||
|
|
07d7c9df4b | ||
|
|
6765a1d025 | ||
|
|
952f28a872 | ||
|
|
8246b5a42c | ||
|
|
72123aeeb7 | ||
|
|
236d1ae25f | ||
|
|
b75369b28d | ||
|
|
63871db170 | ||
|
|
6456c2f6e2 | ||
|
|
36faa194a8 | ||
|
|
093dc87aad | ||
|
|
ca045e3ebf | ||
|
|
be5e2f371d | ||
|
|
6d15465bac | ||
|
|
2031a5b0c0 | ||
|
|
7b3844a391 | ||
|
|
202de36042 | ||
|
|
62837b512b | ||
|
|
5d5a99fd77 | ||
|
|
1b5effd584 | ||
|
|
1613ab2c9e | ||
|
|
a2df4e35be | ||
|
|
aabbcfdf6b | ||
|
|
7b7759e1ce | ||
|
|
e5a3053600 | ||
|
|
9856d0ef58 | ||
|
|
77b37e5503 | ||
|
|
793319fe38 | ||
|
|
594a895ddd | ||
|
|
0d59266b8d | ||
|
|
4863aafaa8 | ||
|
|
6015320145 | ||
|
|
8c09c0bce0 | ||
|
|
437943b779 | ||
|
|
6a090e45b6 | ||
|
|
a7dc314204 | ||
|
|
37076eae89 | ||
|
|
9a9f8dc847 | ||
|
|
719b657267 | ||
|
|
671a615501 | ||
|
|
884b45bf12 | ||
|
|
ff6ae1b066 | ||
|
|
d581fec55e | ||
|
|
a5613a79bb | ||
|
|
c6703e16e8 | ||
|
|
6587dce95b | ||
|
|
b60e6448b9 | ||
|
|
46d9b58b32 | ||
|
|
349cbabc94 | ||
|
|
9de056a3fc | ||
|
|
ad497b322f | ||
|
|
7a6408ee44 | ||
|
|
336743f2b4 | ||
|
|
6e32eb3bd6 | ||
|
|
b377b83442 | ||
|
|
899b94bb6e | ||
|
|
d97efdff61 | ||
|
|
1097afbcb8 | ||
|
|
165b2d3568 | ||
|
|
44a80c8985 | ||
|
|
6e32d9b743 | ||
|
|
6b167e7a4c | ||
|
|
011672b1ed | ||
|
|
a7eb926810 | ||
|
|
cad7f13a0e | ||
|
|
65fcea817a | ||
|
|
22fade13e7 | ||
|
|
7146310072 | ||
|
|
b7de1c25ec | ||
|
|
f4c7f47ffe | ||
|
|
cdf339d077 | ||
|
|
31702df19b | ||
|
|
cf0d1c0473 | ||
|
|
1f369f9848 | ||
|
|
2372ff5f4f | ||
|
|
6d0e34ba1d | ||
|
|
7a69698178 | ||
|
|
532bf84e06 | ||
|
|
e1afbc02c4 | ||
|
|
756f00d88d | ||
|
|
b72e404d52 | ||
|
|
e31cdeef42 | ||
|
|
8a4e32cc27 | ||
|
|
f285505b79 | ||
|
|
67f5c50300 | ||
|
|
ce39e4f779 | ||
|
|
9c80f5664c | ||
|
|
83466c001b | ||
|
|
ad315be503 | ||
|
|
c41ae2dcc8 | ||
| d9f8e92fac | |||
|
|
d42ede15db | ||
|
|
08ea9f7c00 | ||
|
|
c327e1fc0d | ||
|
|
02fddcf9a1 | ||
|
|
4752bfbb05 | ||
|
|
767ddd7add | ||
|
|
54a24ced83 | ||
|
|
57ae2981f1 | ||
|
|
a3043ac685 | ||
|
|
2bdb90b0be | ||
|
|
2cd846d504 | ||
|
|
f593c7bec9 | ||
|
|
c8805db0b5 | ||
|
|
1eb6d8cec9 | ||
|
|
ed19ee03af | ||
|
|
a1c931b5e6 | ||
|
|
cb553a31ab | ||
|
|
557df272ff | ||
|
|
0cb3c96c15 | ||
|
|
1cb39d4d61 | ||
|
|
934b7d4f8a | ||
|
|
24c50cde56 | ||
|
|
055e8d2f13 | ||
|
|
abfe7fe339 | ||
|
|
16ed91f6a9 | ||
|
|
67ba2a5286 | ||
|
|
3d7f9b71e1 | ||
|
|
1d972fad11 | ||
|
|
e23bc461c4 | ||
|
|
28ed458b83 | ||
|
|
827210e010 | ||
|
|
de2f32da36 | ||
|
|
6fa0fc5dc6 | ||
|
|
73e3d9d419 | ||
|
|
5f2229e3d5 | ||
|
|
d19c4a517c | ||
|
|
c47480f60c | ||
|
|
295211bb72 | ||
|
|
818bc3cc2b | ||
|
|
bb6111e2b3 | ||
|
|
512f05c0b2 | ||
|
|
500fb8124f | ||
|
|
88ddba2a4b | ||
|
|
86f6d06b85 | ||
|
|
1cefc0e7ee | ||
|
|
412182a022 | ||
|
|
43b8ee1c7e | ||
|
|
83eea02240 | ||
|
|
a14e31804a | ||
|
|
3fa639ebf7 | ||
|
|
ed11d44fe8 | ||
|
|
de4ea8888f | ||
|
|
ea0a936871 | ||
|
|
042987c91e | ||
|
|
6669e74baa | ||
|
|
906f9a139d | ||
|
|
b31f960635 | ||
|
|
1ed3cb31b5 | ||
|
|
ca7c332f20 | ||
|
|
d587b44b34 | ||
|
|
e43aac972a | ||
|
|
7dbe31b5d3 | ||
|
|
1672c96340 | ||
|
|
453aee2c1f | ||
|
|
00abbdcd65 | ||
|
|
88685dc585 | ||
|
|
cf8147d6ca | ||
|
|
f28f6b1530 | ||
|
|
cb4ea71e81 | ||
|
|
fe94d3781a | ||
|
|
7c17b39dad | ||
|
|
73dab4c651 | ||
|
|
5d72843167 | ||
|
|
f43df47cff | ||
|
|
aaca26770b | ||
|
|
4a124d5bd8 | ||
|
|
03cd52a01a | ||
|
|
da79181b00 | ||
|
|
a405104052 | ||
|
|
56fdaa65d2 | ||
|
|
9d60c49c9f | ||
|
|
380170d5ba | ||
|
|
4b450f2091 | ||
|
|
1b013fbf1c | ||
|
|
bf7459bd2d | ||
|
|
1096db0846 | ||
|
|
fc9b765246 | ||
|
|
4a8f7bb014 | ||
|
|
e0469be0d8 | ||
|
|
e9e576b5ec | ||
|
|
1a32d114f1 | ||
|
|
94e83f6b8d | ||
|
|
35a6dede6f | ||
|
|
039ec397dd | ||
|
|
dce4630eb8 | ||
|
|
1e142f042b | ||
|
|
7caca2f139 | ||
|
|
61556b2de8 | ||
|
|
9e12409389 | ||
|
|
c8050f5a79 | ||
|
|
cb4f71c7cd | ||
|
|
a39f4e4eee | ||
|
|
b525b4171d | ||
|
|
f2d80b3b63 | ||
|
|
96dda87f37 | ||
|
|
3435509eec | ||
|
|
42f2767129 | ||
|
|
099a5f7840 | ||
|
|
c83ea705a2 | ||
|
|
9ea6c875f2 | ||
|
|
0728f72c4f | ||
|
|
a90a6f00cf | ||
|
|
f71e53af8d | ||
|
|
cc1b971e19 | ||
|
|
8484c752ed | ||
|
|
8bd31e6c97 | ||
|
|
f9b0f87f44 | ||
|
|
d7e9f10291 | ||
|
|
3edc3587b3 | ||
|
|
63bf00e28f | ||
|
|
6b2574947a | ||
|
|
eb9027ecb6 | ||
|
|
2ad931a566 | ||
|
|
cebe119304 | ||
|
|
bd3082d7e3 | ||
|
|
11da53f1cb | ||
|
|
c4cb0b2395 | ||
|
|
58614e9a3d | ||
|
|
c94c334c4b | ||
|
|
3bf44405f8 | ||
|
|
db68a256cb | ||
|
|
611fac6338 | ||
|
|
ddd120bb75 | ||
|
|
0d75e0bad8 | ||
|
|
ba8c4ac844 | ||
|
|
6938a42896 | ||
|
|
efff77c94e | ||
|
|
2b10cf6192 | ||
|
|
134b91f266 | ||
|
|
969f551e10 | ||
|
|
26623ba085 | ||
|
|
8eb495890d | ||
|
|
c1a30977f1 | ||
|
|
9a6be61651 | ||
|
|
388839039e | ||
|
|
e760a89a6a | ||
|
|
d2e30267e8 | ||
|
|
190dbddfe3 | ||
|
|
05e769794e | ||
|
|
558ebaf6f2 | ||
|
|
970b56fe5a | ||
|
|
c2597bd07b | ||
|
|
c4baca477b | ||
|
|
7726b0e0b0 | ||
|
|
0a536245f3 | ||
|
|
edfa9ecc07 | ||
|
|
f4982c85ca | ||
|
|
3ce771d587 | ||
|
|
72d6b446c3 | ||
|
|
40d694eec8 | ||
|
|
6af1423666 | ||
|
|
23a01347f1 | ||
|
|
7c86d6163a | ||
|
|
d7233208f1 | ||
|
|
7f587ba720 | ||
|
|
21ecc1b675 | ||
|
|
6b13dc2b87 | ||
|
|
bb89184212 | ||
|
|
56338caaca | ||
|
|
d7e2ff8ac9 | ||
|
|
582ec0a1f9 | ||
|
|
53785f5644 | ||
|
|
a8acadb8f1 | ||
|
|
23dbda302e | ||
|
|
7e7ab4275d | ||
|
|
a455a3d98d | ||
|
|
588d987c25 | ||
|
|
8ffd06343a | ||
|
|
76207c443c | ||
|
|
7e9e0fac07 | ||
|
|
1fdbc7cce9 | ||
|
|
312369b200 | ||
|
|
c030174bfb | ||
|
|
faf466e79a | ||
|
|
856ffc1b72 | ||
|
|
c0924af06b | ||
|
|
33cec61a13 | ||
|
|
854ff696d8 | ||
|
|
cc3435d13d | ||
|
|
5ceaac195d | ||
|
|
5d5df977a6 | ||
|
|
245e85ef36 | ||
|
|
b96df6f14f | ||
|
|
1932c1e138 | ||
|
|
f7ee012be3 | ||
|
|
b49e11bc79 | ||
|
|
f709a11dfb | ||
|
|
17ed2240d3 | ||
|
|
a8b5107b1a | ||
|
|
735bab5d26 | ||
|
|
86f7876f50 | ||
|
|
46c96bc7ec | ||
|
|
cba3f5972b | ||
|
|
ed48c49964 | ||
|
|
203ff3f80d | ||
|
|
cfe90a8d0d | ||
|
|
0749ea0819 | ||
|
|
e09a39e748 | ||
|
|
20dcefaefd | ||
|
|
4c8347516a | ||
|
|
e497d226a0 | ||
|
|
ccf8f1acc0 | ||
|
|
fe9a2ed979 | ||
|
|
078815e83e | ||
|
|
bb80c7af5a | ||
|
|
842662aae1 | ||
|
|
876fac2332 | ||
|
|
427deca350 | ||
|
|
07bde4b25c | ||
|
|
80b06af581 | ||
|
|
3c026811e9 | ||
|
|
cf547ce7e4 | ||
|
|
6952c79899 | ||
|
|
09e3f63da7 | ||
|
|
d6956f4122 | ||
|
|
6fca73e7da | ||
|
|
880df4138c | ||
|
|
badf5b5761 | ||
|
|
b174aa25b3 | ||
|
|
bc6eac6977 | ||
|
|
2d4d595e5d | ||
|
|
7111fe14fb | ||
|
|
8499e38c55 | ||
|
|
682d80b1b8 | ||
|
|
4fe6f233f2 | ||
|
|
6ba3588eff | ||
|
|
53aa403630 | ||
|
|
cba9255732 | ||
|
|
a7b78c73c4 | ||
|
|
85e0b6a4a9 | ||
|
|
3c7e762eeb | ||
|
|
9a1f7d0985 | ||
|
|
dfd60200fb | ||
|
|
713f7544cd | ||
|
|
de4e96ab01 | ||
|
|
3e7b15c7db | ||
|
|
eb2ccfbbca | ||
|
|
70e508e583 | ||
|
|
8b0acf2eae | ||
|
|
d254870439 | ||
|
|
303225cb85 | ||
|
|
90de167643 | ||
|
|
8ee997de8e | ||
|
|
e27f6ac716 | ||
|
|
ec4de95fcf | ||
|
|
df0fa95519 | ||
|
|
2fe7fba79b | ||
|
|
e40234ee29 | ||
|
|
a287cc27a7 | ||
|
|
921186eb74 | ||
|
|
6fd215ceff | ||
|
|
0297509aa0 | ||
|
|
c5a8bc745d | ||
|
|
36aa466561 | ||
|
|
009485e5b9 | ||
|
|
eb01c8772d | ||
|
|
e3fb69928c | ||
|
|
65afa14466 | ||
|
|
0176b19616 | ||
|
|
9f3dfd9a59 | ||
|
|
513ea354ab | ||
|
|
7b686e11c9 | ||
|
|
3997000266 | ||
|
|
6e177d4cae | ||
|
|
3554991ff8 | ||
|
|
58d2d6382a | ||
|
|
67f28a366c | ||
|
|
64f53acce2 | ||
|
|
2e790f69a1 | ||
|
|
e62011d97e | ||
|
|
cd8d16d09f | ||
|
|
9c61965d4b | ||
|
|
61b8cb5dea | ||
|
|
899f6d9558 | ||
|
|
899de27617 | ||
|
|
322c488e72 | ||
|
|
5d5e66a1c5 | ||
|
|
5f4e9d541d | ||
|
|
306ea93be2 | ||
|
|
3fd83a3160 | ||
|
|
56c1397547 | ||
|
|
90adf8dcdd | ||
|
|
6e0fe0ff25 | ||
|
|
794ceec33c | ||
|
|
11d073c10a | ||
|
|
c160b5058b | ||
|
|
2f49738cf9 | ||
|
|
bfe4b1ec8b | ||
|
|
6224844915 | ||
|
|
81a17bc0c2 | ||
|
|
f4c2be7411 | ||
|
|
16489a9494 | ||
|
|
0e03b06a4b | ||
|
|
c3b89c7f7d | ||
|
|
142b46570d | ||
|
|
f7fac50108 | ||
|
|
f7078957cf | ||
|
|
f258d4f2f1 | ||
|
|
ae5fbcf857 | ||
|
|
2579b4c929 | ||
|
|
b69c3b7a78 | ||
|
|
67df4dffeb | ||
|
|
9cf2cc3c96 | ||
|
|
dd94215c3b | ||
|
|
1fd1a8e7ce | ||
|
|
90a831f59b | ||
|
|
9fb218f0be | ||
|
|
bdd22abc55 | ||
|
|
75f712a305 | ||
|
|
e3991c46e2 | ||
|
|
ccc9dfa4aa | ||
|
|
a37a72b7db | ||
|
|
724d3b91a0 | ||
|
|
887c797e13 | ||
|
|
0fd1e470e8 | ||
|
|
13ecab8390 | ||
|
|
15d9db822b | ||
|
|
dd355d1ddf | ||
|
|
c6dcf919e2 | ||
|
|
42541373af | ||
|
|
208c88f5a4 | ||
|
|
5605bc74df | ||
|
|
4bec574011 | ||
|
|
8cfa37ce8b | ||
|
|
351ceeb357 | ||
|
|
259bcc0173 | ||
|
|
db195a8311 | ||
|
|
4307bfb1b5 | ||
|
|
df46ee6939 | ||
|
|
70b9b71104 | ||
|
|
cd33355418 | ||
|
|
77d1cdb753 | ||
|
|
545218e3b9 | ||
|
|
cb07305a87 | ||
|
|
76a39cad20 | ||
|
|
ebd567af48 | ||
|
|
a9c418e3f2 | ||
|
|
e143acd67d | ||
|
|
84eb2f1845 | ||
|
|
b075e5d5d5 | ||
|
|
a08f6aeea2 | ||
|
|
469bd574ac | ||
|
|
c41918c0a8 | ||
|
|
0361ca574d |
@@ -117,8 +117,54 @@
|
||||
"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
|
||||
"skipCi": true,
|
||||
"commitType": "docs"
|
||||
}
|
||||
|
||||
8
.devcontainer/Dockerfile
Normal file
8
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
||||
# 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
|
||||
26
.devcontainer/devcontainer.json
Normal file
26
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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"
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
#!/bin/bash
|
||||
sasjs lint
|
||||
|
||||
# Ensure lint is passing
|
||||
LINT=`sasjs lint`
|
||||
if [[ "$LINT" != *"All matched files use @sasjs/lint code style!" ]]; then
|
||||
echo "$LINT"
|
||||
echo "To commit in spite of these warnings, use the -n parameter."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Avoid commits to the master branch
|
||||
BRANCH=`git rev-parse --abbrev-ref HEAD`
|
||||
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
5
.github/CONTRIBUTING.md
vendored
5
.github/CONTRIBUTING.md
vendored
@@ -12,7 +12,7 @@ This repository makes use of the [SASjs](https://sasjs.io) framework for code or
|
||||
* [VSCode](https://sasjs.io/windows/#vscode) - feature packed IDE for code editing (warning - highly effective!)
|
||||
* [GIT](https://sasjs.io/windows/#git) - a safety net you cannot (and should not) do without.
|
||||
|
||||
For generating the documentation (`sasjs doc`) it is also necessary to install [doxygen](https://www.doxygen.nl/manual/install.html).
|
||||
For generating the documentation (`sasjs doc`) it is also necessary to install [doxygen](https://www.doxygen.nl/manual/install.html) and GraphViz (`sudo port install graphviz` on mac, or `sudo apt-get install graphviz` on Ubuntu).
|
||||
|
||||
|
||||
To get configured:
|
||||
@@ -27,5 +27,6 @@ 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. Push and make a PR
|
||||
4. Commit using a [Conventional Commit](https://www.conventionalcommits.org)
|
||||
5. Push and make a PR
|
||||
|
||||
|
||||
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
custom: https://getalby.com/p/sasjs
|
||||
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
## Issue
|
||||
|
||||
Link any related issue(s) in this section.
|
||||
|
||||
## Intent
|
||||
|
||||
What this PR intends to achieve.
|
||||
|
||||
## Implementation
|
||||
|
||||
What code changes have been made to achieve the intent.
|
||||
|
||||
## Checks
|
||||
|
||||
- [ ] Code is formatted correctly (`sasjs lint`).
|
||||
- [ ] Any new functionality has been unit tested.
|
||||
- [ ] All unit tests are passing (`sasjs test`).
|
||||
- [ ] The PR desc or underlying commits follow the [Conventional Commit](https://www.conventionalcommits.org) standard
|
||||
9
.github/dependabot.yml
vendored
Normal file
9
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: monthly
|
||||
open-pull-requests-limit: 3
|
||||
allow:
|
||||
- dependency-type: "production"
|
||||
41
.github/vpn/config.ovpn
vendored
41
.github/vpn/config.ovpn
vendored
@@ -1,30 +1,25 @@
|
||||
cipher AES-256-CBC
|
||||
setenv FORWARD_COMPATIBLE 1
|
||||
# Client
|
||||
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
|
||||
tls-client
|
||||
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
|
||||
# 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
|
||||
|
||||
# Keys
|
||||
ca ca.crt
|
||||
cert user.crt
|
||||
key user.key
|
||||
tls-auth tls.key 1
|
||||
|
||||
# Security
|
||||
nobind
|
||||
persist-key
|
||||
persist-tun
|
||||
verb 3
|
||||
|
||||
53
.github/workflows/main.yml
vendored
53
.github/workflows/main.yml
vendored
@@ -13,9 +13,54 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Semantic Release
|
||||
uses: cycjimmy/semantic-release-action@v2
|
||||
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:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
CA_CRT: ${{ secrets.CA_CRT}}
|
||||
USER_CRT: ${{ secrets.USER_CRT }}
|
||||
USER_KEY: ${{ secrets.USER_KEY }}
|
||||
TLS_KEY: ${{ secrets.TLS_KEY }}
|
||||
|
||||
- name: Install Open VPN
|
||||
run: |
|
||||
#sudo apt install apt-transport-https
|
||||
#sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
|
||||
#sudo apt-key add openvpn-repo-pkg-key.pub
|
||||
#sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-jammy.list
|
||||
#sudo apt update
|
||||
#sudo apt install openvpn3=17~betaUb22042+jammy
|
||||
- name: Start Open VPN 3
|
||||
run: |
|
||||
# openvpn3 session-start --config .github/vpn/config.ovpn
|
||||
|
||||
- name: Add credentials
|
||||
run: |
|
||||
echo "CLIENT=${{secrets.SAS9_4GL_IO_CLIENT}}"> .env.server
|
||||
echo "ACCESS_TOKEN=${{secrets.SAS9_4GL_IO_ACCESS_TOKEN}}" >> .env.server
|
||||
echo "REFRESH_TOKEN=${{secrets.SAS9_4GL_IO_REFRESH_TOKEN}}" >> .env.server
|
||||
|
||||
- name: Semantic Release
|
||||
uses: cycjimmy/semantic-release-action@v4
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: SAS Packages Release
|
||||
run: |
|
||||
npx @sasjs/cli compile job -s sasjs/utils/create_sas_package.sas -o sasjsbuild -t server
|
||||
# need long duration token per https://github.com/sasjs/server/issues/307
|
||||
# npx @sasjs/cli run sasjsbuild/jobs/utils/create_sas_package.sas -t server
|
||||
|
||||
32
.github/workflows/notmain.yml
vendored
Normal file
32
.github/workflows/notmain.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
|
||||
|
||||
name: SASjs Core - Update all.sas
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm ci
|
||||
npm i -g @sasjs/cli@latest
|
||||
|
||||
- name: Ensure all.sas is always up to date
|
||||
run: |
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
python3 build.py
|
||||
git add all.sas
|
||||
git commit -m "chore: updating all.sas" --allow-empty
|
||||
git push
|
||||
|
||||
|
||||
31
.github/workflows/run-tests.yml
vendored
31
.github/workflows/run-tests.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [lts/fermium]
|
||||
node-version: [lts/hydrogen]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -39,46 +39,39 @@ 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-focal.list
|
||||
sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-jammy.list
|
||||
sudo apt update
|
||||
sudo apt install openvpn3
|
||||
|
||||
sudo apt install openvpn3=17~betaUb22042+jammy
|
||||
- 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
|
||||
run: npm run lint
|
||||
- name: Check code style (aborts if errors found)
|
||||
run: npx @sasjs/cli lint
|
||||
|
||||
- name: Add client
|
||||
run: echo "CLIENT=${{secrets.CLIENT}}"> .env.viya
|
||||
|
||||
- name: Add secret
|
||||
run: echo "SECRET=${{secrets.SECRET}}" >> .env.viya
|
||||
run: echo "CLIENT=${{secrets.SAS9_4GL_IO_CLIENT}}"> .env.server
|
||||
|
||||
- name: Add access token
|
||||
run: echo "ACCESS_TOKEN=${{secrets.ACCESS_TOKEN}}" >> .env.viya
|
||||
run: echo "ACCESS_TOKEN=${{secrets.SAS9_4GL_IO_ACCESS_TOKEN}}" >> .env.server
|
||||
|
||||
- name: Add refresh token
|
||||
run: echo "REFRESH_TOKEN=${{secrets.REFRESH_TOKEN}}" >> .env.viya
|
||||
run: echo "REFRESH_TOKEN=${{secrets.SAS9_4GL_IO_REFRESH_TOKEN}}" >> .env.server
|
||||
|
||||
- name: Build Project
|
||||
run: npm run build
|
||||
- name: Build & Deploy Project to SAS server
|
||||
run: npx @sasjs/cli cbd -t server
|
||||
|
||||
- name: Run SASjs tests
|
||||
run: npm run test
|
||||
- name: Run all tests
|
||||
run: npx @sasjs/cli test -t server
|
||||
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
5
.gitignore
vendored
@@ -10,4 +10,7 @@ sasjsresults/
|
||||
mc_*
|
||||
|
||||
# ignore .env files as they can contain sasjs access tokens
|
||||
*.env*
|
||||
*.env*
|
||||
|
||||
~
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM gitpod/workspace-full
|
||||
|
||||
RUN sudo apt-get update \
|
||||
&& sudo apt-get install -y \
|
||||
doxygen \
|
||||
&& sudo apt-get install -y doxygen \
|
||||
&& sudo apt-get install -y graphviz \
|
||||
&& sudo rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
tasks:
|
||||
- init: nvm install --lts && npm i -g @sasjs/cli
|
||||
- init: npm install -g npm
|
||||
- command: npm i
|
||||
- command: npm i -g @sasjs/cli
|
||||
|
||||
image:
|
||||
file: .gitpod.dockerfile
|
||||
@@ -22,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
|
||||
|
||||
@@ -6,7 +6,6 @@ sasjs/
|
||||
.github/
|
||||
.git-hooks/
|
||||
.vscode/
|
||||
main.dox
|
||||
make_singlefile.sh
|
||||
*.md
|
||||
.all-contributorsrc
|
||||
|
||||
26
.sasjslint
26
.sasjslint
@@ -1,13 +1,15 @@
|
||||
{
|
||||
"noTrailingSpaces": true,
|
||||
"noEncodedPasswords": true,
|
||||
"hasDoxygenHeader": true,
|
||||
"hasMacroNameInMend": true,
|
||||
"hasMacroParentheses": true,
|
||||
"noNestedMacros": false,
|
||||
"noSpacesInFileNames": true,
|
||||
"maxLineLength": 300,
|
||||
"lowerCaseFileNames": true,
|
||||
"noTabIndentation": true,
|
||||
"indentationMultiple": 2
|
||||
}
|
||||
"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
|
||||
}
|
||||
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -6,5 +6,7 @@
|
||||
"editor.rulers": [
|
||||
80
|
||||
],
|
||||
"files.trimTrailingWhitespace": true
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"sasjs-for-vscode.target": "docsonly",
|
||||
"sasjs-for-vscode.isLocal": true
|
||||
}
|
||||
193
README.md
193
README.md
@@ -2,8 +2,6 @@
|
||||
[![npm package][npm-image]][npm-url]
|
||||
[![Github Workflow][githubworkflow-image]][githubworkflow-url]
|
||||

|
||||

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

|
||||
[](https://github.com/sasjs/core/issues?q=is%3Aissue+is%3Aclosed)
|
||||
[](https://github.com/sasjs/core/issues)
|
||||
@@ -18,7 +16,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/CONTRIBUTING.md) are welcomed.
|
||||
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.
|
||||
|
||||
You can download and compile them all in just two lines of SAS code:
|
||||
|
||||
@@ -31,57 +29,34 @@ Documentation: https://core.sasjs.io
|
||||
|
||||
## Components
|
||||
|
||||
### BASE library (SAS9/Viya)
|
||||
### BASE folder (All Platforms)
|
||||
|
||||
- OS independent
|
||||
- Not metadata aware
|
||||
- Works on all SAS Platforms
|
||||
- No X command
|
||||
- Prefixes: _mf_, _mp_
|
||||
- Prefixes: `mf_`, `mp_`
|
||||
|
||||
### DDL folder (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)
|
||||
|
||||
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 (SAS9/Viya)
|
||||
- Function and macro names are identical, except for special cases
|
||||
- Prefixes: _mcf_
|
||||
- Prefixes: `mcf_`
|
||||
|
||||
The fcmp macros are used to generate fcmp functions, and can be used with or
|
||||
without the `proc fcmp` wrapper.
|
||||
The fcmp macros are used to generate fcmp functions, and can be used with or without the `proc fcmp` wrapper.
|
||||
|
||||
### 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
|
||||
### LUA folder
|
||||
|
||||
Wait - this is a macro library - what is LUA doing here? Well, it is a little known fact that you CAN run LUA within a SAS Macro. It has to be written to a text file with a `.lua` extension, from where you can `%include` it. So, without using the `proc lua` wrapper.
|
||||
|
||||
To contribute, simply write your freeform LUA in the LUA folder. Then run the `build.py`, which will convert your LUA into a data step with put statements, and create the macro wrapper with a `ml_` prefix. You can then use your module in any program by running:
|
||||
To contribute, simply write your freeform LUA in the LUA folder. Then run the `build.py`, which will convert all files with a ".lua" extension into a macro wrapper with an `ml_` prefix (embedding the necessary data step put statements). You can then use your module in any program by running:
|
||||
|
||||
```sas
|
||||
/* compile the lua module */
|
||||
@@ -95,16 +70,63 @@ 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: _mmw_,_mmu_,_mmx_
|
||||
- Prefixes: `mmx_`
|
||||
|
||||
### SERVER folder (@sasjs/server only)
|
||||
These macros are used for building applications using [@sasjs/server](https://server.sasjs.io) - an open source REST API for Desktop SAS.
|
||||
|
||||
- OS independent
|
||||
- @sasjs/server aware
|
||||
- No X command
|
||||
- Prefixes: `ms_`
|
||||
|
||||
### VIYA folder (Viya only)
|
||||
|
||||
Macros used for interfacing with SAS Viya.
|
||||
|
||||
- OS independent
|
||||
- No X command
|
||||
- Prefixes: `mv_`, `mvf_`
|
||||
|
||||
### XPLATFORM folder (Viya, Meta, and Server)
|
||||
|
||||
Sometimes it is helpful to use a macro that can be used interchangeably regardless of the server type on which is is running (SASVIYA, SAS9, SASJS).
|
||||
|
||||
- OS independent
|
||||
- No X command
|
||||
- Prefixes: `mx_`
|
||||
|
||||
## Installation
|
||||
|
||||
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
|
||||
options insert=(sasautos="/your/path/macrocore/base");
|
||||
options insert=(sasautos="/your/path/macrocore/meta");
|
||||
%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");
|
||||
```
|
||||
|
||||
The above can be done directly in your sas program, via an autoexec, or an initialisation program.
|
||||
@@ -125,14 +147,16 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||
- macro names must be lowercase
|
||||
- one macro per file
|
||||
- prefixes:
|
||||
- _mcf_ for macro compiled functions (proc fcmp)
|
||||
- _mddl_ for macros containing DDL (Data Definition Language)
|
||||
- _mf_ for macro functions (can be used in open code).
|
||||
- _ml_ for macros that are used to compile LUA modules
|
||||
- _mm_ for metadata macros (interface with the metadata server).
|
||||
- _mmx_ for macros that use metadata and are XCMD enabled
|
||||
- _mmx_ for macros that use metadata and are XCMD enabled (working on both windows and unix)
|
||||
- _mp_ for macro procedures (which generate sas code)
|
||||
- _ms_ for macro procedures that will only work with [@sasjs/server](https://github.com/sasjs/server)
|
||||
- _mv_ for macro procedures that will only work in Viya
|
||||
- _mx_ for macros that are XCMD enabled
|
||||
- _mx_ for macros that work on Viya, SAS 9 EBI and SASjs Server
|
||||
- follow verb-noun convention
|
||||
- unix style line endings (lf)
|
||||
- individual lines should be no more than 80 characters long
|
||||
@@ -166,7 +190,7 @@ SAS code can contain one of two types of dependency - SAS Macros, and SAS Includ
|
||||
@li someprogram.sas FREFTWO
|
||||
```
|
||||
|
||||
The CLI can then extract all the dependencies and insert as precode (SAS Macros) or in a temp engine fileref (SAS Includes) when creating SAS Jobs and Services.
|
||||
The CLI can then extract all the dependencies and insert as precode (SAS Macros) or in a temp engine fileref (SAS Includes) when creating SAS Jobs and Services (and Tests).
|
||||
|
||||
When contributing to this library, it is therefore important to ensure that all dependencies are listed in the header in this format.
|
||||
|
||||
@@ -180,13 +204,27 @@ When contributing to this library, it is therefore important to ensure that all
|
||||
- The closing `%mend;` should **not** contain the macro name.
|
||||
- All macros should be defined with brackets, even if no variables are needed - ie `%macro x();` not `%macro x;`
|
||||
- Mandatory parameters should be positional, all optional parameters should be keyword (var=) style.
|
||||
- All dataset references must be 2 level (eg `work.blah`, not `blah`). This is to avoid contention when options [DATASTMTCHK](https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000279064.htm)=ALLKEYWORDS is in effect.
|
||||
- All dataset references must be 2 level (eg `work.blah`, not `blah`). This is to avoid contention when options [DATASTMTCHK](https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000279064.htm)=ALLKEYWORDS is in effect, or the [USER](https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/lrcon/n18m1vkqmeo4esn1moikt23zhp8s.htm) library is active.
|
||||
- Avoid naming collisions! All macro variables should be local scope. Use system generated work tables where possible - eg `data ; set sashelp.class; run; data &output; set &syslast; run;`
|
||||
- Where global macro variables are absolutely necessary, they should make use of `&sasjs_prefix` - see mp_init.sas
|
||||
- The use of `quit;` for `proc sql` is optional unless you are looking to benefit from the timing statistics.
|
||||
- Use [sasjs lint](https://github.com/sasjs/lint)!
|
||||
|
||||
## General Notes
|
||||
|
||||
- All macros should be compatible with SAS versions from support level B and above (so currently 9.2 and later). If an earlier version is not supported, then the macro should say as such in the header documentation, and exit gracefully (eg `%if %sysevalf(&sysver<9.3) %then %return`).
|
||||
- All macros should be compatible with SAS versions from support level B and above (so currently 9.3 and later). If an earlier version is not supported, then the macro should say as such in the header documentation, and exit gracefully.
|
||||
- It's [best to avoid](https://git.datacontroller.io/dc/dc/issues/50) special / non-ASCII characters for compatibility with the widest variety of SAS installations.
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
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.
|
||||
* mp_getddl.sas to be renamed to mp_ds2ddl.sas (consistent with other ds2xxx macros). A wrapper macro is already in place, and you are able to use this immediately. The default for SHOWLOG will also be YES instead of NO.
|
||||
* mp_coretable.sas will be replaced by the standalone macros in the `ddl` folder (which are already available)
|
||||
|
||||
## Star Gazing
|
||||
|
||||
@@ -194,11 +232,23 @@ If you find this library useful, please leave a [star](https://github.com/sasjs/
|
||||
|
||||

|
||||
|
||||
## 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 -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
||||
@@ -206,20 +256,29 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Allan Bowe</b></sub></a><br /><a href="#business-allanbowe" title="Business development">💼</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Code">💻</a> <a href="#content-allanbowe" title="Content">🖋</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Documentation">📖</a> <a href="#infra-allanbowe" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-allanbowe" title="Maintenance">🚧</a> <a href="#mentoring-allanbowe" title="Mentoring">🧑🏫</a> <a href="#question-allanbowe" title="Answering Questions">💬</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3Aallanbowe" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/rafgag"><img src="https://avatars.githubusercontent.com/u/69139928?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rafgag</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=rafgag" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/tmoody"><img src="https://avatars.githubusercontent.com/u/79837106?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Trevor Moody</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=tmoody" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://krishna-acondy.io/"><img src="https://avatars.githubusercontent.com/u/2980428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Krishna Acondy</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=krishna-acondy" title="Code">💻</a> <a href="#infra-krishna-acondy" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-krishna-acondy" title="Blogposts">📝</a> <a href="#content-krishna-acondy" title="Content">🖋</a> <a href="#ideas-krishna-acondy" title="Ideas, Planning, & Feedback">🤔</a> <a href="#video-krishna-acondy" title="Videos">📹</a></td>
|
||||
<td align="center"><a href="https://github.com/saadjutt01"><img src="https://avatars.githubusercontent.com/u/8914650?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muhammad Saad </b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=saadjutt01" title="Code">💻</a> <a href="#ideas-saadjutt01" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://www.erudicat.com/"><img src="https://avatars.githubusercontent.com/u/25773492?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yury Shkoda</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=YuryShkoda" title="Code">💻</a> <a href="#infra-YuryShkoda" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#video-YuryShkoda" title="Videos">📹</a></td>
|
||||
<td align="center"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="#infra-medjedovicm" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/kkchandok"><img src="https://avatars.githubusercontent.com/u/46090627?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kkchandok</b></sub></a><br /><a href="#ideas-kkchandok" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<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>
|
||||
<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>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
@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
|
||||
|
||||
@@ -11,8 +16,8 @@
|
||||
@cond
|
||||
**/
|
||||
|
||||
%macro mf_abort(mac=mf_abort.sas, type=deprecated, msg=, iftrue=%str(1=1)
|
||||
)/*/STORE SOURCE*/;
|
||||
%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)
|
||||
)/des='ungraceful abort' /*STORE SOURCE*/;
|
||||
|
||||
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||
|
||||
@@ -24,4 +29,4 @@
|
||||
|
||||
%mend mf_abort;
|
||||
|
||||
/** @endcond */
|
||||
/** @endcond */
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mf_trimstr.sas
|
||||
@li mf_wordsinstr1butnotstr2.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
31
base/mf_deletefile.sas
Normal file
31
base/mf_deletefile.sas
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
@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;
|
||||
@@ -10,7 +10,7 @@
|
||||
expected results (depending on whether you 'expect' the result to be
|
||||
case insensitive in this context!)
|
||||
|
||||
@param libds library.dataset
|
||||
@param [in] libds library.dataset
|
||||
@return output returns 1 or 0
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
|
||||
@@ -9,19 +9,17 @@
|
||||
|
||||
%put %mf_existfeature(PROCLUA);
|
||||
|
||||
@param feature the feature to detect. Leave blank to list all in log.
|
||||
@param [in] feature The feature to detect.
|
||||
|
||||
@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);
|
||||
@@ -29,7 +27,11 @@
|
||||
%let platform=%mf_getplatform();
|
||||
|
||||
%if &feature= %then %do;
|
||||
%put Supported features: PROCLUA;
|
||||
%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;
|
||||
%end;
|
||||
%else %if &feature=PROCLUA %then %do;
|
||||
/* https://blogs.sas.com/content/sasdummy/2015/08/03/using-lua-within-your-sas-programs */
|
||||
@@ -38,10 +40,20 @@
|
||||
%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 */
|
||||
|
||||
@@ -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 fref the fileref to detect
|
||||
@param [in] fref the fileref to detect
|
||||
|
||||
@return output Returns 1 if found and 0 if not found. Note - it is possible
|
||||
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;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionexists-macro
|
||||
).
|
||||
|
||||
@param [in] name (positional) - function name
|
||||
@param [in] name function name
|
||||
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
@file
|
||||
@brief Checks if a variable exists in a data set.
|
||||
@details Returns 0 if the variable does NOT exist, and return the position of
|
||||
the var if it does.
|
||||
Usage:
|
||||
@details Returns 0 if the variable does NOT exist, and the position of the var
|
||||
if it does.
|
||||
Usage:
|
||||
|
||||
%put %mf_existvar(work.someds, somevar)
|
||||
%put %mf_existvar(work.someds, somevar)
|
||||
|
||||
@param [in] libds 2 part dataset or view reference
|
||||
@param [in] var variable name
|
||||
@@ -25,13 +25,17 @@
|
||||
%local dsid rc;
|
||||
%let dsid=%sysfunc(open(&libds,is));
|
||||
|
||||
%if &dsid=0 or %length(&var)=0 %then %do;
|
||||
%if &dsid=0 %then %do;
|
||||
%put %sysfunc(sysmsg());
|
||||
0
|
||||
0
|
||||
%end;
|
||||
%else %if %length(&var)=0 %then %do;
|
||||
0
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%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;
|
||||
|
||||
@@ -6,11 +6,8 @@
|
||||
|
||||
%put %mf_existVarList(sashelp.class, age sex name dummyvar);
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_abort.sas
|
||||
|
||||
@param libds 2 part dataset or view reference
|
||||
@param varlist space separated variable names
|
||||
@param [in] libds 2 part dataset or view reference
|
||||
@param [in] varlist space separated variable names
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
42
base/mf_fmtdttm.sas
Normal file
42
base/mf_fmtdttm.sas
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
@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;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
@li /data
|
||||
@li /jobs
|
||||
@li /services
|
||||
@li /tests
|
||||
@li /tests/jobs
|
||||
@li /tests/services
|
||||
@li /tests/macros
|
||||
@@ -30,6 +31,7 @@
|
||||
%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
|
||||
**/
|
||||
@@ -46,9 +48,13 @@
|
||||
|
||||
/**
|
||||
* First check we are not in the tests/macros folder (which has no subfolders)
|
||||
* or specifically in the testsetup or testteardown services
|
||||
*/
|
||||
%if %index(&pgm,/tests/macros/) %then %do;
|
||||
%let root=%substr(&pgm,1,%index(&pgm,/tests/macros)-1);
|
||||
%if %index(&pgm,/tests/macros/)
|
||||
or %index(&pgm,/tests/testsetup)
|
||||
or %index(&pgm,/tests/testteardown)
|
||||
%then %do;
|
||||
%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);
|
||||
&root
|
||||
%return;
|
||||
%end;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
%put Dataset label = %mf_getattrc(sashelp.class,LABEL);
|
||||
%put Member Type = %mf_getattrc(sashelp.class,MTYPE);
|
||||
|
||||
@param libds library.dataset
|
||||
@param attr full list in [documentation](
|
||||
@param [in] libds library.dataset
|
||||
@param [in] 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.
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
%put Number of observations=%mf_getattrn(sashelp.class,NLOBS);
|
||||
%put Number of variables = %mf_getattrn(sashelp.class,NVARS);
|
||||
|
||||
@param libds library.dataset
|
||||
@param attr Common values are NLOBS and NVARS, full list in [documentation](
|
||||
@param [in] libds library.dataset
|
||||
@param [in] attr Common values are NLOBS and NVARS, full list in [documentation](
|
||||
http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212040.htm)
|
||||
@return output returns result of the attrn value supplied, or -1 and log
|
||||
message if err.
|
||||
|
||||
@@ -5,18 +5,19 @@
|
||||
|
||||
%put %mf_getfilesize(fpath=C:\temp\myfile.txt);
|
||||
|
||||
or
|
||||
or, provide a libds value as follows:
|
||||
|
||||
data x;do x=1 to 100000;y=x;output;end;run;
|
||||
%put %mf_getfilesize(libds=work.x,format=yes);
|
||||
|
||||
gives:
|
||||
Which gives:
|
||||
|
||||
2mb
|
||||
> 2mb
|
||||
|
||||
@param [in] fpath= Full path and filename. Provide this OR the libds value.
|
||||
@param [in] libds= (0) Library.dataset value (assumes library is BASE engine)
|
||||
@param [in] format= (NO) Set to yes to apply sizekmg. format
|
||||
|
||||
@param fpath= full path and filename. Provide this OR the libds value.
|
||||
@param libds= library.dataset value (assumes library is BASE engine)
|
||||
@param format= set to yes to apply sizekmg. format
|
||||
@returns bytes
|
||||
|
||||
@version 9.2
|
||||
@@ -26,16 +27,32 @@
|
||||
%macro mf_getfilesize(fpath=,libds=0,format=NO
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if &libds ne 0 %then %do;
|
||||
%let fpath=%sysfunc(pathname(%scan(&libds,1,.)))/%scan(&libds,2,.).sas7bdat;
|
||||
%end;
|
||||
%local rc fid fref bytes dsid lib vnum;
|
||||
|
||||
%local rc fid fref bytes;
|
||||
%let rc=%sysfunc(filename(fref,&fpath));
|
||||
%let fid=%sysfunc(fopen(&fref));
|
||||
%let bytes=%sysfunc(finfo(&fid,File Size (bytes)));
|
||||
%let rc=%sysfunc(fclose(&fid));
|
||||
%let rc=%sysfunc(filename(fref));
|
||||
%if &libds ne 0 %then %do;
|
||||
%let libds=%upcase(&libds);
|
||||
%if %index(&libds,.)=0 %then %let lib=WORK;
|
||||
%else %let lib=%scan(&libds,1,.);
|
||||
%let dsid=%sysfunc(open(
|
||||
sashelp.vtable(where=(libname="&lib" and memname="%scan(&libds,-1,.)")
|
||||
keep=libname memname filesize
|
||||
)
|
||||
));
|
||||
%if (&dsid ^= 0) %then %do;
|
||||
%let vnum=%sysfunc(varnum(&dsid,FILESIZE));
|
||||
%let rc=%sysfunc(fetch(&dsid));
|
||||
%let bytes=%sysfunc(getvarn(&dsid,&vnum));
|
||||
%let rc= %sysfunc(close(&dsid));
|
||||
%end;
|
||||
%else %put &sysmacroname: &libds could not be opened! %sysfunc(sysmsg());
|
||||
%end;
|
||||
%else %do;
|
||||
%let rc=%sysfunc(filename(fref,&fpath));
|
||||
%let fid=%sysfunc(fopen(&fref));
|
||||
%let bytes=%sysfunc(finfo(&fid,File Size (bytes)));
|
||||
%let rc=%sysfunc(fclose(&fid));
|
||||
%let rc=%sysfunc(filename(fref));
|
||||
%end;
|
||||
|
||||
%if &format=NO %then %do;
|
||||
&bytes
|
||||
|
||||
@@ -10,10 +10,9 @@
|
||||
|
||||
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.
|
||||
|
||||
|
||||
37
base/mf_getgitbranch.sas
Normal file
37
base/mf_getgitbranch.sas
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
@file
|
||||
@brief Retrieves the current branch from a local GIT repo
|
||||
@details In a local git repository, the current branch is always available in
|
||||
the `.git/HEAD` file in a format like this: `ref: refs/heads/master`
|
||||
|
||||
This macro simply reads the file and returns the last word (eg `master`).
|
||||
|
||||
Example usage:
|
||||
|
||||
%let gitdir=%sysfunc(pathname(work))/core;
|
||||
%let repo=https://github.com/sasjs/core;
|
||||
%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&gitdir));
|
||||
|
||||
%put The current branch is %mf_getgitbranch(&gitdir);
|
||||
|
||||
@param [in] gitdir The directory containing the GIT repository
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_readfile.sas
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_gitadd.sas
|
||||
@li mp_gitlog.sas
|
||||
@li mp_gitreleaseinfo.sas
|
||||
@li mp_gitstatus.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_getgitbranch(gitdir
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%scan(%mf_readfile(&gitdir/.git/HEAD),-1)
|
||||
|
||||
%mend mf_getgitbranch;
|
||||
@@ -7,8 +7,9 @@
|
||||
%put %mf_getkeyvalue(someindex)
|
||||
|
||||
|
||||
@param key Provide a key on which to perform the lookup
|
||||
@param libds= define the target table which holds the parameters
|
||||
@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
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
@@ -5,10 +5,14 @@
|
||||
|
||||
%put %mf_getplatform();
|
||||
|
||||
returns:
|
||||
SASMETA (or SASVIYA)
|
||||
returns one of:
|
||||
|
||||
@param switch the param for which to return a platform specific variable
|
||||
@li SASMETA
|
||||
@li SASVIYA
|
||||
@li SASJS
|
||||
@li BASESAS
|
||||
|
||||
@param [in] switch the param for which to return a platform specific variable
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_mval.sas
|
||||
@@ -22,6 +26,12 @@
|
||||
)/*/STORE SOURCE*/;
|
||||
%local a b c;
|
||||
%if &switch.NONE=NONE %then %do;
|
||||
%if %symexist(sasjsprocessmode) %then %do;
|
||||
%if &sasjsprocessmode=Stored Program %then %do;
|
||||
SASJS
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%if %symexist(sysprocessmode) %then %do;
|
||||
%if "&sysprocessmode"="SAS Object Server"
|
||||
or "&sysprocessmode"= "SAS Compute Server" %then %do;
|
||||
@@ -62,4 +72,4 @@
|
||||
%else %if &switch=VIYARESTAPI %then %do;
|
||||
%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)
|
||||
%end;
|
||||
%mend mf_getplatform;
|
||||
%mend mf_getplatform;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
returns:
|
||||
> dbo
|
||||
|
||||
@param libref Library reference (also accepts a 2 level libds ref).
|
||||
@param [in] libref Library reference (also accepts a 2 level libds ref).
|
||||
|
||||
@return output returns the library schema for the FIRST library encountered
|
||||
|
||||
|
||||
@@ -28,15 +28,17 @@
|
||||
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);
|
||||
%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);
|
||||
%local rc fname;
|
||||
%if &prefix=0 %then %do;
|
||||
%let rc=%sysfunc(filename(fname,,temp));
|
||||
%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));
|
||||
%if &rc %then %put %sysfunc(sysmsg());
|
||||
&fname
|
||||
%end;
|
||||
@@ -47,7 +49,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));
|
||||
%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));
|
||||
%if &rc %then %put %sysfunc(sysmsg());
|
||||
&fname
|
||||
%return;
|
||||
|
||||
@@ -3,38 +3,55 @@
|
||||
@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
|
||||
|
||||
@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.
|
||||
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.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
|
||||
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||
%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;
|
||||
%local x;
|
||||
|
||||
%if ( %length(&prefix) gt 7 ) %then %do;
|
||||
%put %str(ERR)OR: The prefix parameter cannot exceed 7 characters.;
|
||||
0
|
||||
%return;
|
||||
%end;
|
||||
%else %if (%sysfunc(NVALID(&prefix,v7))=0) %then %do;
|
||||
%put %str(ERR)OR: Invalid prefix (&prefix);
|
||||
0
|
||||
%return;
|
||||
%end;
|
||||
%put unable to find available libref in range &prefix.0-&maxtries;
|
||||
|
||||
/* Set maxtries equal to '10 to the power of [# unused characters] - 1' */
|
||||
%let maxtries=%eval(10**(8-%length(&prefix))-1);
|
||||
|
||||
%do x = 0 %to &maxtries;
|
||||
%if %sysfunc(libref(&prefix&x)) ne 0 %then %do;
|
||||
&prefix&x
|
||||
%return;
|
||||
%end;
|
||||
%let x = %eval(&x + 1);
|
||||
%end;
|
||||
|
||||
%put %str(ERR)OR: No usable libref in range &prefix.0-&maxtries;
|
||||
%put %str(ERR)OR- Try reducing the prefix or deleting some libraries!;
|
||||
0
|
||||
%mend mf_getuniquelibref;
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
> MCc59c750610321d4c8bf75faadbcd22
|
||||
|
||||
@param prefix= set a prefix for the new name
|
||||
@param [in] prefix= (MC) Sets a prefix for the new name
|
||||
|
||||
@version 9.3
|
||||
@author Allan Bowe
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
%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)
|
||||
@@ -23,17 +21,19 @@
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_getuser(type=META
|
||||
%macro mf_getuser(
|
||||
)/*/STORE SOURCE*/;
|
||||
%local user metavar;
|
||||
%if &type=OS %then %let metavar=_secureusername;
|
||||
%else %let metavar=_metaperson;
|
||||
%local user;
|
||||
|
||||
%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;
|
||||
%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;
|
||||
/* sometimes SAS will add @domain extension - remove for consistency */
|
||||
%else %let user=%scan(&&&metavar,1,@);
|
||||
/* but be sure to quote in case of usernames with commas */
|
||||
%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));
|
||||
%end;
|
||||
%else %let user=&sysuserid;
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_setkeyvalue.sas
|
||||
|
||||
@param libds dataset to query
|
||||
@param variable the variable which contains the value to return.
|
||||
@param filter contents of where clause
|
||||
@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
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
@@ -2,31 +2,48 @@
|
||||
@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 Number of Variables=%mf_getvarcount(sashelp.class);
|
||||
%put Character Variables=%mf_getvarcount(sashelp.class,typefilter=C);
|
||||
%put Numeric Variables = %mf_getvarcount(sashelp.class,typefilter=N);
|
||||
|
||||
returns:
|
||||
> Number of Variables=4
|
||||
|
||||
@param libds Two part dataset (or view) reference.
|
||||
|
||||
@param [in] libds Two part dataset (or view) reference.
|
||||
@param [in] typefilter= (A) Filter for certain types of column. Valid values:
|
||||
@li A Count All columns
|
||||
@li C Count Character columns only
|
||||
@li N Count Numeric columns only
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mf_getvarcount(libds
|
||||
%macro mf_getvarcount(libds,typefilter=A
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dsid nvars rc ;
|
||||
%local dsid nvars rc outcnt x;
|
||||
%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;
|
||||
&nvars
|
||||
&outcnt
|
||||
%mend mf_getvarcount;
|
||||
@@ -25,7 +25,8 @@
|
||||
|
||||
@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
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
8
|
||||
NOTE: Variable renegade does not exist in test
|
||||
|
||||
@param libds Two part dataset (or view) reference.
|
||||
@param var Variable name for which a length should be returned
|
||||
@param [in] libds Two part dataset (or view) reference.
|
||||
@param [in] var Variable name for which a length should be returned
|
||||
@returns outputs length
|
||||
|
||||
@author Allan Bowe
|
||||
|
||||
@@ -21,8 +21,8 @@ returns:
|
||||
|
||||
> NOTE: Variable renegade does not exist in test
|
||||
|
||||
@param libds Two part dataset (or view) reference.
|
||||
@param var Variable name for which a position should be returned
|
||||
@param [in] libds Two part dataset (or view) reference.
|
||||
@param [in] var Variable name for which a position should be returned
|
||||
|
||||
@author Allan Bowe
|
||||
@version 9.2
|
||||
|
||||
@@ -13,8 +13,8 @@ Usage:
|
||||
|
||||
|
||||
|
||||
@param libds Two part dataset (or view) reference.
|
||||
@param var the variable name to be checked
|
||||
@param [in] libds Two part dataset (or view) reference.
|
||||
@param [in] 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.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
returns:
|
||||
> TEMP
|
||||
|
||||
@param fref The fileref to check
|
||||
@param [in] fref The fileref to check
|
||||
|
||||
@returns The XENGINE value in sashelp.vextfl or 0 if not found.
|
||||
|
||||
|
||||
29
base/mf_increment.sas
Normal file
29
base/mf_increment.sas
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
@file
|
||||
@brief Increments a macro variable
|
||||
@details Useful outside of do-loops - will increment a macro variable every
|
||||
time it is called.
|
||||
|
||||
Example:
|
||||
|
||||
%let cnt=1;
|
||||
%put We have run %mf_increment(cnt) lines;
|
||||
%put Now we have run %mf_increment(cnt) lines;
|
||||
%put There are %mf_increment(cnt) lines in total;
|
||||
|
||||
@param [in] macro_name The name of the macro variable to increment
|
||||
@param [in] incr= (1) The amount to add or subtract to the macro
|
||||
|
||||
<h4> Related Files </h4>
|
||||
@li mf_increment.test.sas
|
||||
|
||||
**/
|
||||
|
||||
%macro mf_increment(macro_name,incr=1);
|
||||
|
||||
/* iterate the value */
|
||||
%let ¯o_name=%eval(&&¯o_name+&incr);
|
||||
/* return the value */
|
||||
&&¯o_name
|
||||
|
||||
%mend mf_increment;
|
||||
@@ -7,12 +7,12 @@
|
||||
|
||||
Usage:
|
||||
|
||||
%put mf_isblank(&var);
|
||||
%put %mf_isblank(&var);
|
||||
|
||||
inspiration:
|
||||
https://support.sas.com/resources/papers/proceedings09/022-2009.pdf
|
||||
|
||||
@param param VALUE to be checked
|
||||
@param [in] Param VALUE to be checked
|
||||
|
||||
@return output returns 1 (if blank) else 0
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
With thanks and full credit to Andrea Defronzo -
|
||||
https://www.linkedin.com/in/andrea-defronzo-b1a47460/
|
||||
|
||||
@param path full path of the file/directory to be checked
|
||||
@param [in] path Full path of the file/directory to be checked
|
||||
|
||||
@return output returns 1 if path is a directory, 0 if it is not
|
||||
|
||||
|
||||
@@ -20,8 +20,11 @@
|
||||
|
||||
%macro mf_isint(arg
|
||||
)/*/STORE SOURCE*/;
|
||||
/* remove minus sign if exists */
|
||||
|
||||
/* 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;
|
||||
@@ -30,4 +33,4 @@
|
||||
%if %sysfunc(findc(%str(&val),,kd)) %then %do;0%end;
|
||||
%else %do;1%end;
|
||||
|
||||
%mend mf_isint;
|
||||
%mend mf_isint;
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
%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
|
||||
**/
|
||||
@@ -15,7 +19,8 @@
|
||||
%local root;
|
||||
|
||||
%if &loc=POF or &loc=PLATFORMOBJECTFRAMEWORK %then %do;
|
||||
%let root=%substr(%sysget(SASROOT),1,%index(%sysget(SASROOT),SASFoundation)-2);
|
||||
%let root=%sysget(SASROOT);
|
||||
%let root=%substr(&root,1,%index(&root,SASFoundation)-2);
|
||||
%let root=&root/SASPlatformObjectFramework/&sysver;
|
||||
%put Batch tools located at: &root;
|
||||
&root
|
||||
|
||||
@@ -7,7 +7,7 @@ Usage:
|
||||
%mf_mkdir(/some/path/name)
|
||||
|
||||
|
||||
@param dir relative or absolute pathname. Unquoted.
|
||||
@param [in] dir Relative or absolute pathname. Unquoted.
|
||||
@version 9.2
|
||||
|
||||
**/
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
%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
|
||||
**/
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getattrn.sas
|
||||
|
||||
@param libds library.dataset
|
||||
@param [in] libds library.dataset
|
||||
|
||||
@return output returns result of the attrn value supplied, or log message
|
||||
if err.
|
||||
|
||||
63
base/mf_readfile.sas
Normal file
63
base/mf_readfile.sas
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
@file
|
||||
@brief Reads the first line of a file using pure macro
|
||||
@details Reads the first line of a file and returns it. Future versions may
|
||||
read each line into a macro variable array.
|
||||
|
||||
Generally, reading data into macro variables is not great as certain
|
||||
nonprintable characters (such as CR, LF) may be dropped in the conversion.
|
||||
|
||||
Usage:
|
||||
|
||||
%mf_writefile(&sasjswork/myfile.txt,l1=some content,l2=more content)
|
||||
|
||||
%put %mf_readfile(&sasjswork/myfile.txt);
|
||||
|
||||
|
||||
@param [in] fpath Full path to file to be read
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mf_deletefile.sas
|
||||
@li mf_writefile.sas
|
||||
@li mf_readfile.test.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
/** @cond */
|
||||
|
||||
%macro mf_readfile(fpath
|
||||
)/*/STORE SOURCE*/;
|
||||
%local fref rc fid fcontent;
|
||||
|
||||
/* check file exists */
|
||||
%if %sysfunc(filename(fref,&fpath)) ne 0 %then %do;
|
||||
%put &=fref &=fpath;
|
||||
%put %str(ERR)OR: %sysfunc(sysmsg());
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%let fid=%sysfunc(fopen(&fref,I));
|
||||
|
||||
%if &fid=0 %then %do;
|
||||
%put %str(ERR)OR: %sysfunc(sysmsg());
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%if %sysfunc(fread(&fid)) = 0 %then %do;
|
||||
%let rc=%sysfunc(fget(&fid,fcontent,65534));
|
||||
&fcontent
|
||||
%end;
|
||||
|
||||
/*
|
||||
%do %while(%sysfunc(fread(&fid)) = 0);
|
||||
%let rc=%sysfunc(fget(&fid,fcontent,65534));
|
||||
&fcontent
|
||||
%end;
|
||||
*/
|
||||
|
||||
%let rc=%sysfunc(fclose(&fid));
|
||||
%let rc=%sysfunc(filename(&fref));
|
||||
|
||||
%mend mf_readfile;
|
||||
/** @endcond */
|
||||
@@ -11,8 +11,8 @@
|
||||
<h4> SAS Macros </h4>
|
||||
|
||||
|
||||
@param basestr The string to be modified
|
||||
@param trimstr The string to be removed from the end of `basestr`, if it
|
||||
@param [in] basestr The string to be modified
|
||||
@param [in] 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
@file
|
||||
@brief Checks if a set of macro variables exist / contain values.
|
||||
@brief Checks if a set of macro variables exist AND contain values.
|
||||
@details Writes ERROR to log if abortType is SOFT, else will call %mf_abort.
|
||||
Usage:
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_abort.sas
|
||||
|
||||
@param verifyvars space separated list of macro variable names
|
||||
@param makeupcase= set to YES to convert all variable VALUES to
|
||||
@param [in] verifyvars Space separated list of macro variable names
|
||||
@param [in] makeupcase= (NO) Set to YES to convert all variable VALUES to
|
||||
uppercase.
|
||||
@param mAbort= Abort Type. Default is SOFT (writes err to log).
|
||||
@param [in] mAbort= (SOFT) Abort Type. When SOFT, simply writes an err
|
||||
message to the log.
|
||||
Set to any other value to call mf_abort (which can be configured to abort in
|
||||
various fashions according to context).
|
||||
|
||||
@@ -58,8 +59,14 @@
|
||||
|
||||
%goto exit_success;
|
||||
%exit_err:
|
||||
%if &mAbort=SOFT %then %put %str(ERR)OR: &abortmsg;
|
||||
%else %mf_abort(mac=mf_verifymacvars,type=&mabort,msg=&abortmsg);
|
||||
%put &abortmsg;
|
||||
%mf_abort(iftrue=(&mabort ne SOFT),
|
||||
mac=mf_verifymacvars,
|
||||
msg=%str(&abortmsg)
|
||||
)
|
||||
0
|
||||
%return;
|
||||
%exit_success:
|
||||
1
|
||||
|
||||
%mend mf_verifymacvars;
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
returns:
|
||||
> blah blaaah brah
|
||||
|
||||
@param str1= string containing words to extract
|
||||
@param 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
|
||||
|
||||
@warning CASE SENSITIVE!
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
%local count_base count_extr i i2 extr_word base_word match outvar;
|
||||
%if %length(&str1)=0 or %length(&str2)=0 %then %do;
|
||||
%put %str(WARN)ING: empty string provided!;
|
||||
%put base string (str1)= &str1;
|
||||
%put compare string (str2) = &str2;
|
||||
%return;
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
@brief Returns words that are in string 1 but not in string 2
|
||||
@details Compares two space separated strings and returns the words that are
|
||||
in the first but not in the second.
|
||||
|
||||
Note - case sensitive!
|
||||
|
||||
Usage:
|
||||
|
||||
%let x= %mf_wordsInStr1ButNotStr2(
|
||||
@@ -13,10 +16,8 @@
|
||||
returns:
|
||||
> sss bram boo
|
||||
|
||||
@param str1= string containing words to extract
|
||||
@param str2= used to compare with the extract string
|
||||
|
||||
@warning CASE SENSITIVE!
|
||||
@param [in] str1= () String containing words to extract
|
||||
@param [in] str2= () Used to compare with the extract string
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -30,7 +31,6 @@
|
||||
|
||||
%local count_base count_extr i i2 extr_word base_word match outvar;
|
||||
%if %length(&str1)=0 or %length(&str2)=0 %then %do;
|
||||
%put %str(WARN)ING: empty string provided!;
|
||||
%put base string (str1)= &str1;
|
||||
%put compare string (str2) = &str2;
|
||||
%return;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,27 +8,34 @@
|
||||
|
||||
The method used varies according to the context. Important points:
|
||||
|
||||
@li should not use endsas or abort cancel in 9.4m3 environments as this can
|
||||
cause hung multibridge sessions and result in a frozen STP server
|
||||
@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 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. We take a unique "soft abort" approach - we open a macro
|
||||
and set SYSCC=0.
|
||||
Where possible, we take a unique "soft abort" approach - we open a macro
|
||||
but don't close it! This works everywhere EXCEPT inside a \%include inside
|
||||
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 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.
|
||||
@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.
|
||||
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).
|
||||
@@ -39,17 +46,18 @@
|
||||
@li msg (the message)
|
||||
@li mac (the mac param)
|
||||
|
||||
@param mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked for
|
||||
an abort status.
|
||||
@param [in] 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
|
||||
**/
|
||||
|
||||
@@ -58,160 +66,233 @@
|
||||
, mode=REGULAR
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%global sysprocessmode sysprocessname;
|
||||
%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;
|
||||
%local fref fid i;
|
||||
|
||||
%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) %then %do;
|
||||
%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
|
||||
data &errds;
|
||||
iftrue='1=1';
|
||||
length mac $100 msg $5000;
|
||||
mac=symget('mac');
|
||||
msg=symget('msg');
|
||||
run;
|
||||
%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"
|
||||
%then %do;
|
||||
options nosyntaxcheck;
|
||||
%end;
|
||||
|
||||
%if &mode=INCLUDE %then %do;
|
||||
%if %sysfunc(exist(&errds))=1 %then %do;
|
||||
data _null_;
|
||||
abort cancel FILE;
|
||||
set &errds;
|
||||
call symputx('iftrue',iftrue,'l');
|
||||
call symputx('mac',mac,'l');
|
||||
call symputx('msg',msg,'l');
|
||||
putlog (_all_)(=);
|
||||
run;
|
||||
%if (&iftrue)=0 %then %return;
|
||||
%end;
|
||||
%else %do;
|
||||
%put &sysmacroname: No include errors found;
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
/* 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;
|
||||
%if %length(&logloc)>0 %then %do;
|
||||
%let logline=0;
|
||||
/* 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;
|
||||
data _null_;
|
||||
infile &logloc lrecl=5000;
|
||||
input; putlog _infile_;
|
||||
input;
|
||||
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;
|
||||
data _null_;
|
||||
infile &logloc lrecl=5000;
|
||||
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=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;
|
||||
|
||||
%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;
|
||||
|
||||
/* send response in SASjs JSON format */
|
||||
data _null_;
|
||||
file _webout mod lrecl=32000 encoding='utf-8';
|
||||
length msg $32767 ;
|
||||
sasdatetime=datetime();
|
||||
msg=cats(symget('msg'),'\n\nLog Extract:\n',symget('logmsg'));
|
||||
/* 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 '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"';
|
||||
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(),datetime20.3)" '" ';
|
||||
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");
|
||||
i+1;
|
||||
stoploop=1;
|
||||
end;
|
||||
if stoploop=1 then stop;
|
||||
run;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%if %symexist(SYS_JES_JOB_URI) %then %do;
|
||||
/* setup webout for Viya */
|
||||
options nobomfile;
|
||||
%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
|
||||
filename _webout temp lrecl=999999 mod;
|
||||
%end;
|
||||
%else %do;
|
||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"
|
||||
name="_webout.json" lrecl=999999 mod;
|
||||
%end;
|
||||
%end;
|
||||
%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;
|
||||
options nobomfile;
|
||||
/* set up http header for SASjs Server */
|
||||
%let fid=%sysfunc(fopen(&fref,A));
|
||||
%if &fid=0 %then %do;
|
||||
%put %str(ERR)OR: %sysfunc(sysmsg());
|
||||
%return;
|
||||
%end;
|
||||
%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));
|
||||
%let rc=%sysfunc(fwrite(&fid));
|
||||
%let rc=%sysfunc(fclose(&fid));
|
||||
%let rc=%sysfunc(filename(&fref));
|
||||
%end;
|
||||
|
||||
/* send response in SASjs JSON format */
|
||||
data _null_;
|
||||
file _webout mod lrecl=32000 encoding='utf-8';
|
||||
length msg syswarningtext syserrortext $32767 mode $10 ;
|
||||
sasdatetime=datetime();
|
||||
msg=symget('msg');
|
||||
%if &logline>0 %then %do;
|
||||
msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
|
||||
%end;
|
||||
/* escape the escapes */
|
||||
msg=tranwrd(msg,'\','\\');
|
||||
/* escape the quotes */
|
||||
msg=tranwrd(msg,'"','\"');
|
||||
/* ditch the CRLFs as chrome complains */
|
||||
msg=compress(msg,,'kw');
|
||||
/* quote without quoting the quotes (which are escaped instead) */
|
||||
msg=cats('"',msg,'"');
|
||||
if symexist('_debug') then debug=quote(trim(symget('_debug')));
|
||||
else debug='""';
|
||||
if symget('sasjsprocessmode')='Stored Program' then mode='SASJS';
|
||||
if mode ne 'SASJS' then put '>>weboutBEGIN<<';
|
||||
put '{"SYSDATE" : "' "&SYSDATE" '"';
|
||||
put ',"SYSTIME" : "' "&SYSTIME" '"';
|
||||
put ',"sasjsAbort" : [{';
|
||||
put ' "MSG":' msg ;
|
||||
put ' ,"MAC": "' "&mac" '"}]';
|
||||
put ",""SYSUSERID"" : ""&sysuserid"" ";
|
||||
put ',"_DEBUG":' debug ;
|
||||
if symexist('_metauser') then do;
|
||||
_METAUSER=quote(trim(symget('_METAUSER')));
|
||||
put ",""_METAUSER"": " _METAUSER;
|
||||
_METAPERSON=quote(trim(symget('_METAPERSON')));
|
||||
put ',"_METAPERSON": ' _METAPERSON;
|
||||
end;
|
||||
if symexist('SYS_JES_JOB_URI') then do;
|
||||
SYS_JES_JOB_URI=quote(trim(symget('SYS_JES_JOB_URI')));
|
||||
put ',"SYS_JES_JOB_URI": ' SYS_JES_JOB_URI;
|
||||
end;
|
||||
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
||||
put ',"_PROGRAM" : ' _PROGRAM ;
|
||||
put ",""SYSCC"" : ""&syscc"" ";
|
||||
syserrortext=cats(symget('syserrortext'));
|
||||
if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do;
|
||||
syserrortext='"'!!trim(
|
||||
prxchange('s/"/\\"/',-1, /* double quote */
|
||||
prxchange('s/\x0A/\n/',-1, /* new line */
|
||||
prxchange('s/\x0D/\r/',-1, /* carriage return */
|
||||
prxchange('s/\x09/\\t/',-1, /* tab */
|
||||
prxchange('s/\x00/\\u0000/',-1, /* NUL */
|
||||
prxchange('s/\x0E/\\u000E/',-1, /* SS */
|
||||
prxchange('s/\x0F/\\u000F/',-1, /* SF */
|
||||
prxchange('s/\x01/\\u0001/',-1, /* SOH */
|
||||
prxchange('s/\x02/\\u0002/',-1, /* STX */
|
||||
prxchange('s/\x10/\\u0010/',-1, /* DLE */
|
||||
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
|
||||
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
|
||||
prxchange('s/\\/\\\\/',-1,syserrortext)
|
||||
)))))))))))))!!'"';
|
||||
end;
|
||||
else syserrortext=cats('"',syserrortext,'"');
|
||||
put ',"SYSERRORTEXT" : ' syserrortext;
|
||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||
put ",""SYSSITE"" : ""&syssite"" ";
|
||||
sysvlong=quote(trim(symget('sysvlong')));
|
||||
put ',"SYSVLONG" : ' sysvlong;
|
||||
syswarningtext=cats(symget('syswarningtext'));
|
||||
if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do;
|
||||
syswarningtext='"'!!trim(
|
||||
prxchange('s/"/\\"/',-1, /* double quote */
|
||||
prxchange('s/\x0A/\n/',-1, /* new line */
|
||||
prxchange('s/\x0D/\r/',-1, /* carriage return */
|
||||
prxchange('s/\x09/\\t/',-1, /* tab */
|
||||
prxchange('s/\x00/\\u0000/',-1, /* NUL */
|
||||
prxchange('s/\x0E/\\u000E/',-1, /* SS */
|
||||
prxchange('s/\x0F/\\u000F/',-1, /* SF */
|
||||
prxchange('s/\x01/\\u0001/',-1, /* SOH */
|
||||
prxchange('s/\x02/\\u0002/',-1, /* STX */
|
||||
prxchange('s/\x10/\\u0010/',-1, /* DLE */
|
||||
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
|
||||
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
|
||||
prxchange('s/\\/\\\\/',-1,syswarningtext)
|
||||
)))))))))))))!!'"';
|
||||
end;
|
||||
else syswarningtext=cats('"',syswarningtext,'"');
|
||||
put ",""SYSWARNINGTEXT"" : " syswarningtext;
|
||||
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
|
||||
put "}" ;
|
||||
if mode ne 'SASJS' then put '>>weboutEND<<';
|
||||
run;
|
||||
|
||||
%put _all_;
|
||||
|
||||
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
|
||||
data _null_;
|
||||
putlog 'stpsrvset program err and syscc';
|
||||
rc=stpsrvset('program error', 0);
|
||||
call symputx("syscc",0,"g");
|
||||
run;
|
||||
%if &sysscp=WIN
|
||||
and 1=0 /* deprecating this logic until we figure out a consistent abort */
|
||||
and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"
|
||||
and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;
|
||||
/* skip approach (below) does not work in windows m6+ envs */
|
||||
endsas;
|
||||
%end;
|
||||
%else %do;
|
||||
/**
|
||||
* endsas kills 9.4m3 deployments by orphaning multibridges.
|
||||
* Abort variants are ungraceful (non zero return code)
|
||||
@@ -229,28 +310,29 @@
|
||||
run;
|
||||
%inc skip;
|
||||
%end;
|
||||
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
|
||||
/* endsas kills the session making it harder to fetch results */
|
||||
data _null_;
|
||||
syswarningtext=symget('syswarningtext');
|
||||
syserrortext=symget('syserrortext');
|
||||
abort_msg=symget('msg');
|
||||
syscc=symget('syscc');
|
||||
sysuserid=symget('sysuserid');
|
||||
iftrue=symget('iftrue');
|
||||
put (_all_)(/=);
|
||||
call symputx('syscc',0);
|
||||
abort cancel nolist;
|
||||
run;
|
||||
%end;
|
||||
%else %do;
|
||||
%abort cancel;
|
||||
%end;
|
||||
%end;
|
||||
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
|
||||
/* endsas kills the session making it harder to fetch results */
|
||||
data _null_;
|
||||
syswarningtext=symget('syswarningtext');
|
||||
syserrortext=symget('syserrortext');
|
||||
abort_msg=symget('msg');
|
||||
syscc=symget('syscc');
|
||||
sysuserid=symget('sysuserid');
|
||||
iftrue=symget('iftrue');
|
||||
put (_all_)(/=);
|
||||
call symputx('syscc',0);
|
||||
abort cancel nolist;
|
||||
run;
|
||||
%end;
|
||||
%else %do;
|
||||
%put _all_;
|
||||
%abort cancel;
|
||||
%end;
|
||||
%end;
|
||||
%else %do;
|
||||
%put _all_;
|
||||
%abort cancel;
|
||||
%end;
|
||||
%mend mp_abort;
|
||||
|
||||
/** @endcond */
|
||||
/** @endcond */
|
||||
|
||||
95
base/mp_aligndecimal.sas
Normal file
95
base/mp_aligndecimal.sas
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
@file
|
||||
@brief Apply leading blanks to align numbers vertically in a char variable
|
||||
@details This is particularly useful when storing numbers (as character) that
|
||||
need to be sorted.
|
||||
|
||||
It works by splitting the number left and right of the decimal place, and
|
||||
aligning it accordingly. A temporary variable is created as part of this
|
||||
process (which is automatically dropped)
|
||||
|
||||
The macro can be used only in data step, eg as follows:
|
||||
|
||||
data _null_;
|
||||
length myvar $50;
|
||||
do i=1 to 1000 by 50;
|
||||
if mod(i,2)=0 then j=ranuni(0)*i*100;
|
||||
else j=i*100;
|
||||
|
||||
%mp_aligndecimal(myvar,width=7)
|
||||
|
||||
leading_spaces=length(myvar)-length(cats(myvar));
|
||||
putlog +leading_spaces myvar;
|
||||
end;
|
||||
run;
|
||||
|
||||
The generated code will look something like this:
|
||||
|
||||
length aligndp4e49996 $7;
|
||||
if index(myvar,'.') then do;
|
||||
aligndp4e49996=cats(scan(myvar,1,'.'));
|
||||
aligndp4e49996=right(aligndp4e49996);
|
||||
myvar=aligndp4e49996!!'.'!!cats(scan(myvar,2,'.'));
|
||||
end;
|
||||
else do;
|
||||
aligndp4e49996=myvar;
|
||||
aligndp4e49996=right(aligndp4e49996);
|
||||
myvar=aligndp4e49996;
|
||||
end;
|
||||
drop aligndp4e49996;
|
||||
|
||||
Results (myvar variable):
|
||||
|
||||
0.7683559324
|
||||
122.8232796
|
||||
99419.50552
|
||||
42938.5143414
|
||||
763.3799189
|
||||
15170.606073
|
||||
15083.285773
|
||||
85443.198707
|
||||
2022999.2251
|
||||
12038.658867
|
||||
1350582.6734
|
||||
52777.258221
|
||||
11723.347628
|
||||
33101.268376
|
||||
6181622.8603
|
||||
7390614.0669
|
||||
73384.537893
|
||||
1788362.1016
|
||||
2774586.2219
|
||||
7998580.8415
|
||||
|
||||
|
||||
@param [in] var The (data step, character) variable to modify
|
||||
@param [in] width= (8) The number of characters BEFORE the decimal point
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquename.sas
|
||||
|
||||
<h4> Related Programs </h4>
|
||||
@li mp_aligndecimal.test.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_aligndecimal(var,width=8);
|
||||
|
||||
%local tmpvar;
|
||||
%let tmpvar=%mf_getuniquename(prefix=aligndp);
|
||||
length &tmpvar $&width;
|
||||
if index(&var,'.') then do;
|
||||
&tmpvar=cats(scan(&var,1,'.'));
|
||||
&tmpvar=right(&tmpvar);
|
||||
&var=&tmpvar!!'.'!!cats(scan(&var,2,'.'));
|
||||
end;
|
||||
else do;
|
||||
&tmpvar=cats(&var);
|
||||
&tmpvar=right(&tmpvar);
|
||||
&var=&tmpvar;
|
||||
end;
|
||||
drop &tmpvar;
|
||||
|
||||
%mend mp_aligndecimal;
|
||||
@@ -17,8 +17,8 @@
|
||||
%mp_appendfile(baseref=tmp1, appendrefs=tmp2 tmp3)
|
||||
|
||||
|
||||
@param [in] baseref= Fileref of the base file (should exist)
|
||||
@param [in] appendrefs= One or more filerefs to be appended to the base
|
||||
@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
|
||||
fileref. Space separated.
|
||||
|
||||
@version 9.2
|
||||
|
||||
@@ -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 "%str(ERR)OR: " msg;
|
||||
putlog 'ERR' +(-1) "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 "%str(ERR)OR: " msg;
|
||||
putlog 'ERR' +(-1) "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 "%str(ERR)OR: " msg;
|
||||
putlog 'ERR' +(-1) "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 "%str(ERR)OR: " msg;
|
||||
putlog 'ERR' +(-1) "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 "%str(ERR)OR: " msg;
|
||||
putlog 'ERR' +(-1) "OR: " msg;
|
||||
%end;
|
||||
output;
|
||||
end;
|
||||
@@ -142,16 +142,16 @@ run;
|
||||
proc sql noprint;
|
||||
select distinct lib into: liblist separated by ' ' from &inds;
|
||||
%put &=liblist;
|
||||
%do i=1 %to %sysfunc(countw(&liblist));
|
||||
%if %length(&liblist)>0 %then %do i=1 %to %sysfunc(countw(&liblist));
|
||||
%let lib=%scan(&liblist,1);
|
||||
%let engine=%mf_getengine(&lib);
|
||||
%if &engine ne V9 and &engine ne BASE %then %do;
|
||||
%let msg=&lib has &engine engine - formats cannot be applied;
|
||||
proc sql;
|
||||
insert into &outds set lib="&lib",ds="_all_",var="_all", msg="&msg" ;
|
||||
%if &errds=0 %then %put %str(ERR)OR: &msg;
|
||||
%end;
|
||||
%end;
|
||||
quit;
|
||||
|
||||
%if %mf_nobs(&outds)>0 %then %return;
|
||||
|
||||
|
||||
@@ -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|Column &inds contained ALL columns|
|
||||
|User Provided description|PASS|Dataset &inds contained ALL columns|
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
|
||||
|
||||
@param [in] inds The input library.dataset to test for values
|
||||
@param [in] cols= The list of columns to check for
|
||||
@param [in] desc= (Testing observations) The user provided test description
|
||||
@param [in] cols= (0) The list of columns to check for
|
||||
@param [in] desc= (0) 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|Column &inds contained ALL columns|
|
||||
|User Provided description|PASS|Dataset &inds contained ALL columns|
|
||||
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
|
||||
@@ -36,12 +36,13 @@
|
||||
|
||||
|
||||
@param [in] indscol The input library.dataset.column to test for values
|
||||
@param [in] checkvals= A library.dataset.column value containing a UNIQUE
|
||||
@param [in] checkvals= (0) A library.dataset.column value containing a UNIQUE
|
||||
list of values to be compared against the source (indscol).
|
||||
@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|
|
||||
@@ -97,7 +98,7 @@
|
||||
|
||||
%let test=%upcase(&test);
|
||||
|
||||
%if &test ne ALLVALS and &test ne ANYVAL %then %do;
|
||||
%if &test ne ALLVALS and &test ne ANYVAL and &test ne NOVAL %then %do;
|
||||
%mp_abort(
|
||||
mac=&sysmacroname,
|
||||
msg=%str(Invalid test - &test)
|
||||
@@ -108,12 +109,12 @@
|
||||
%let result=-1;
|
||||
%let orig=-1;
|
||||
proc sql noprint;
|
||||
select count(*) into: result
|
||||
select count(*) into: result trimmed
|
||||
from &lib..&ds
|
||||
where &col not in (
|
||||
select &ccol from &clib..&cds
|
||||
);
|
||||
select count(*) into: orig from &lib..&ds;
|
||||
select count(*) into: orig trimmed from &lib..&ds;
|
||||
quit;
|
||||
|
||||
%local notfound tmp1 tmp2;
|
||||
@@ -145,7 +146,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 values "
|
||||
test_comments="&sysmacroname: &lib..&ds..&col has &result/&orig values "
|
||||
!!"not in &clib..&cds..&ccol.. First 10 vals:"!!symget('notfound');
|
||||
%if &test=ANYVAL %then %do;
|
||||
if &result < &orig then test_result='PASS';
|
||||
@@ -153,6 +154,9 @@
|
||||
%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;
|
||||
|
||||
@@ -12,10 +12,6 @@
|
||||
|
||||
%mp_assertdsobs(sashelp.class,test=ATMOST 20) %* pass if <21 obs present;
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_nobs.sas
|
||||
@li mp_abort.sas
|
||||
|
||||
|
||||
@param [in] inds input dataset to test for presence of observations
|
||||
@param [in] desc= (Testing observations) The user provided test description
|
||||
@@ -33,6 +29,11 @@
|
||||
|---|---|---|
|
||||
|User Provided description|PASS|Dataset &inds has XX obs|
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_nobs.sas
|
||||
@li mp_abort.sas
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_assertcolvals.sas
|
||||
@li mp_assert.sas
|
||||
@@ -49,9 +50,10 @@
|
||||
outds=work.test_results
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local nobs;
|
||||
%local nobs ds;
|
||||
%let nobs=%mf_nobs(&inds);
|
||||
%let test=%upcase(&test);
|
||||
%let ds=%mf_getuniquename(prefix=mp_assertdsobs);
|
||||
|
||||
%if %substr(&test.xxxxx,1,6)=EQUALS %then %do;
|
||||
%let val=%scan(&test,2,%str( ));
|
||||
@@ -84,7 +86,7 @@
|
||||
)
|
||||
%end;
|
||||
|
||||
data;
|
||||
data &ds;
|
||||
length test_description $256 test_result $4 test_comments $256;
|
||||
test_description=symget('desc');
|
||||
test_result='FAIL';
|
||||
@@ -110,9 +112,6 @@
|
||||
%end;
|
||||
run;
|
||||
|
||||
%local ds;
|
||||
%let ds=&syslast;
|
||||
|
||||
proc append base=&outds data=&ds;
|
||||
run;
|
||||
|
||||
|
||||
147
base/mp_assertscope.sas
Normal file
147
base/mp_assertscope.sas
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
@file
|
||||
@brief Used to capture scope leakage of macro variables
|
||||
@details
|
||||
|
||||
A common 'difficult to detect' bug in macros is where a nested macro
|
||||
over-writes variables in a higher level macro.
|
||||
|
||||
This assertion takes a snapshot of the macro variables before and after
|
||||
a macro invocation. Differences are captured in the `&outds` table. This
|
||||
makes it easy to detect whether any macro variables were modified or
|
||||
changed.
|
||||
|
||||
The following variables are NOT tested (as they are known, global variables
|
||||
used in SASjs):
|
||||
|
||||
@li &sasjs_prefix._FUNCTIONS
|
||||
|
||||
Global variables are initialised in mp_init.sas - which will also trigger
|
||||
"strict mode" in your SAS session. Whilst this is a default in SASjs
|
||||
produced apps, if you prefer not to use this mode, simply instantiate the
|
||||
following variable to prevent the macro from running: `SASJS_PREFIX`
|
||||
|
||||
Example usage:
|
||||
|
||||
%mp_assertscope(SNAPSHOT)
|
||||
|
||||
%let oops=I did it again;
|
||||
|
||||
%mp_assertscope(COMPARE,
|
||||
desc=Checking macro variables against previous snapshot
|
||||
)
|
||||
|
||||
This macro is designed to work alongside `sasjs test` - for more information
|
||||
about this facility, visit [cli.sasjs.io/test](https://cli.sasjs.io/test).
|
||||
|
||||
@param [in] action (SNAPSHOT) The action to take. Valid values:
|
||||
@li SNAPSHOT - take a copy of the current macro variables
|
||||
@li COMPARE - compare the current macro variables against previous values
|
||||
@param [in] scope= (GLOBAL) The scope of the variables to be checked. This
|
||||
corresponds to the values in the SCOPE column in `sashelp.vmacro`.
|
||||
@param [in] desc= (Testing scope leakage) The user provided test description
|
||||
@param [in] ignorelist= Provide a list of macro variable names to ignore from
|
||||
the comparison
|
||||
@param [in,out] scopeds= (work.mp_assertscope) The dataset to contain the
|
||||
scope snapshot
|
||||
@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|
|
||||
|---|---|---|
|
||||
|User Provided description|PASS|No out of scope variables created or modified|
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getquotedstr.sas
|
||||
@li mp_init.sas
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_assert.sas
|
||||
@li mp_assertcols.sas
|
||||
@li mp_assertcolvals.sas
|
||||
@li mp_assertdsobs.sas
|
||||
@li mp_assertscope.test.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_assertscope(action,
|
||||
desc=Testing Scope Leakage,
|
||||
scope=GLOBAL,
|
||||
scopeds=work.mp_assertscope,
|
||||
ignorelist=,
|
||||
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);
|
||||
|
||||
/**
|
||||
* this sets up the global vars, it will also enter STRICT mode. If this
|
||||
* behaviour is not desired, simply initiate the following global macro
|
||||
* variable to prevent the macro from running: SASJS_PREFIX
|
||||
*/
|
||||
%mp_init()
|
||||
|
||||
/* get current variables */
|
||||
%if &action=SNAPSHOT %then %do;
|
||||
proc sql;
|
||||
create table &scopeds as
|
||||
select name,offset,value
|
||||
from dictionary.macros
|
||||
where scope="&scope" and upcase(name) not in (%mf_getquotedstr(&ilist))
|
||||
order by name,offset;
|
||||
%end;
|
||||
%else %if &action=COMPARE %then %do;
|
||||
|
||||
proc sql;
|
||||
create table _data_ as
|
||||
select name,offset,value
|
||||
from dictionary.macros
|
||||
where scope="&scope" and upcase(name) not in (%mf_getquotedstr(&ilist))
|
||||
order by name,offset;
|
||||
|
||||
%let ds=&syslast;
|
||||
|
||||
proc compare
|
||||
base=&scopeds(where=(upcase(name) not in (%mf_getquotedstr(&ilist))))
|
||||
compare=&ds noprint;
|
||||
run;
|
||||
|
||||
%if &sysinfo=0 %then %do;
|
||||
%let test_result=PASS;
|
||||
%let test_comments=&scope Variables Unmodified;
|
||||
%end;
|
||||
%else %do;
|
||||
proc sql noprint undo_policy=none;
|
||||
select distinct name into: del separated by ' ' from &scopeds
|
||||
where name not in (select name from &ds);
|
||||
select distinct name into: add separated by ' ' from &ds
|
||||
where name not in (select name from &scopeds);
|
||||
select distinct a.name into: mod separated by ' '
|
||||
from &scopeds a
|
||||
inner join &ds b
|
||||
on a.name=b.name
|
||||
and a.offset=b.offset
|
||||
where a.value ne b.value;
|
||||
%let test_result=FAIL;
|
||||
%let test_comments=%str(Mod:(&mod) Add:(&add) Del:(&del));
|
||||
%end;
|
||||
|
||||
|
||||
data ;
|
||||
length test_description $256 test_result $4 test_comments $256;
|
||||
test_description=symget('desc');
|
||||
test_comments=symget('test_comments');
|
||||
test_result=symget('test_result');
|
||||
run;
|
||||
|
||||
%let ds=&syslast;
|
||||
proc append base=&outds data=&ds;
|
||||
run;
|
||||
proc sql;
|
||||
drop table &ds;
|
||||
%end;
|
||||
|
||||
%mend mp_assertscope;
|
||||
@@ -28,8 +28,8 @@
|
||||
put _infile_;
|
||||
run;
|
||||
|
||||
@param [in] inref= Fileref of the input file (should exist)
|
||||
@param [out] outref= Output fileref. If it does not exist, it is created.
|
||||
@param [in] 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] action= (ENCODE) The action to take. Valid values:
|
||||
@li ENCODE - Convert the file to base64 format
|
||||
@li DECODE - Decode the file from base64 format
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
@details Reads in a file byte by byte and writes it back out. Is an
|
||||
os-independent method to copy files. In case of naming collision, the
|
||||
default filerefs can be modified.
|
||||
Based on:
|
||||
https://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file
|
||||
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.
|
||||
|
||||
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
|
||||
|
||||
@@ -22,15 +22,17 @@
|
||||
%mp_binarycopy(inref=tmp1, outref=tmp2, mode=APPEND)
|
||||
|
||||
|
||||
@param [in] inloc quoted "path/and/filename.ext" of the file to be copied
|
||||
@param [out] outloc quoted "path/and/filename.ext" of the file to be created
|
||||
@param [in] inref (____in) If provided, this fileref will take precedence over
|
||||
@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
|
||||
the `inloc` parameter
|
||||
@param [out] outref (____in) If provided, this fileref will take precedence
|
||||
@param [out] outref= (____in) If provided, this fileref takes precedence
|
||||
over the `outloc` parameter. It must already exist!
|
||||
@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
|
||||
|
||||
@@ -44,15 +46,14 @@
|
||||
,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 outmode;
|
||||
%if &mode=APPEND %then %do;
|
||||
%let mod=mod;
|
||||
%let outmode='a';
|
||||
%end;
|
||||
%else %do;
|
||||
%let outmode='o';
|
||||
%end;
|
||||
%local mod;
|
||||
|
||||
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||
|
||||
%if &mode=APPEND %then %let mod=mod;
|
||||
|
||||
/* these IN and OUT filerefs can point to anything */
|
||||
%if &inref = ____in %then %do;
|
||||
filename &inref &inloc lrecl=1048576 ;
|
||||
@@ -63,22 +64,17 @@
|
||||
|
||||
/* copy the file byte-for-byte */
|
||||
data _null_;
|
||||
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);
|
||||
infile &inref lrecl=1 recfm=n;
|
||||
file &outref &mod recfm=n;
|
||||
input sourcechar $char1. @@;
|
||||
format sourcechar hex2.;
|
||||
put sourcechar char1. @@;
|
||||
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;
|
||||
|
||||
195
base/mp_chop.sas
Normal file
195
base/mp_chop.sas
Normal file
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
@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;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
@file mp_cleancsv.sas
|
||||
@file
|
||||
@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,14 +7,17 @@
|
||||
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= 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.
|
||||
@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.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -56,9 +59,14 @@
|
||||
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 ne '0A'x then do;
|
||||
if inchar='0D'x then do;
|
||||
/* multiple CR indicates CR formatted file with blank lines */
|
||||
goto crblank;
|
||||
end;
|
||||
else if inchar ne '0A'x then do;
|
||||
put inchar $char1.;
|
||||
if inchar=qchar then isq = mod(isq+1,2);
|
||||
end;
|
||||
|
||||
94
base/mp_cntlout.sas
Normal file
94
base/mp_cntlout.sas
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
@file mp_cntlout.sas
|
||||
@brief Creates a cntlout dataset in a consistent format
|
||||
@details The dataset produced by proc format in the cntlout option will vary
|
||||
according to its contents.
|
||||
|
||||
When dealing with formats from an ETL perspective (eg in [Data Controller for
|
||||
SAS](https://datacontroller.io)), it is important that the output dataset
|
||||
has a consistent model (and compariable values).
|
||||
|
||||
This macro makes use of mddl_sas_cntlout.sas to provide the consistent model,
|
||||
and will left-align the start and end values when dealing with numeric ranges
|
||||
to enable consistency when checking for differences.
|
||||
|
||||
usage:
|
||||
|
||||
%mp_cntlout(libcat=yourlib.cat,cntlout=work.formatexport)
|
||||
|
||||
@param [in] libcat The library.catalog reference
|
||||
@param [in] fmtlist= (0) provide a space separated list of specific formats to
|
||||
extract
|
||||
@param [in] iftrue= (1=1) A condition under which the macro should be executed
|
||||
@param [out] cntlout= (work.fmtextract) Libds reference for the output dataset
|
||||
|
||||
<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
|
||||
@li mp_getformats.sas
|
||||
@li mp_loadformat.sas
|
||||
@li mp_ds2fmtds.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@cond
|
||||
**/
|
||||
|
||||
%macro mp_cntlout(
|
||||
iftrue=(1=1)
|
||||
,libcat=
|
||||
,cntlout=work.fmtextract
|
||||
,fmtlist=0
|
||||
)/*/STORE SOURCE*/;
|
||||
%local ddlds cntlds i;
|
||||
|
||||
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||
|
||||
%let ddlds=%mf_getuniquename();
|
||||
%let cntlds=%mf_getuniquename();
|
||||
|
||||
%mddl_sas_cntlout(libds=&ddlds)
|
||||
|
||||
%if %index(&libcat,-)>0 and %scan(&libcat,2,-)=FC %then %do;
|
||||
%let libcat=%scan(&libcat,1,-);
|
||||
%end;
|
||||
|
||||
proc format lib=&libcat cntlout=&cntlds;
|
||||
%if "&fmtlist" ne "0" and "&fmtlist" ne "" %then %do;
|
||||
select
|
||||
%do i=1 %to %sysfunc(countw(&fmtlist,%str( )));
|
||||
%scan(&fmtlist,&i,%str( ))
|
||||
%end;
|
||||
;
|
||||
%end;
|
||||
run;
|
||||
|
||||
data &cntlout/nonote2err;
|
||||
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)
|
||||
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;
|
||||
run;
|
||||
|
||||
proc sql;
|
||||
drop table &ddlds,&cntlds;
|
||||
|
||||
%mend mp_cntlout;
|
||||
/** @endcond */
|
||||
@@ -16,8 +16,11 @@
|
||||
|
||||
%mp_copyfolder(&rootdir,©dir)
|
||||
|
||||
@param source Unquoted path to the folder to copy from.
|
||||
@param target Unquoted path to the folder to copy to.
|
||||
@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.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquename.sas
|
||||
@@ -31,7 +34,7 @@
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_copyfolder(source,target);
|
||||
%macro mp_copyfolder(source,target,copymax=MAX);
|
||||
|
||||
%mp_abort(iftrue=(%mf_isdir(&source)=0)
|
||||
,mac=&sysmacroname
|
||||
@@ -50,7 +53,7 @@
|
||||
%let tempds=%mf_getuniquename();
|
||||
|
||||
/* recursive directory listing */
|
||||
%mp_dirlist(path=&source,outds=work.&tempds, maxdepth=MAX)
|
||||
%mp_dirlist(path=&source,outds=work.&tempds,maxdepth=©max)
|
||||
|
||||
/* create folders and copy content */
|
||||
data _null_;
|
||||
@@ -66,7 +69,7 @@
|
||||
rc2=filename(fref2,filepath2,'disk','recfm=n');
|
||||
if fcopy(fref1,fref2) ne 0 then do;
|
||||
msg=sysmsg();
|
||||
putlog "%str(ERR)OR: Unable to copy " filepath " to " filepath2;
|
||||
putlog 'ERR' +(-1) "OR: Unable to copy " filepath " to " filepath2;
|
||||
putlog msg=;
|
||||
end;
|
||||
end;
|
||||
@@ -78,4 +81,4 @@
|
||||
proc sql;
|
||||
drop table work.&tempds;
|
||||
|
||||
%mend mp_copyfolder;
|
||||
%mend mp_copyfolder;
|
||||
|
||||
@@ -5,26 +5,39 @@
|
||||
make use of permanent tables. To avoid duplication in definitions, this
|
||||
macro provides a central location for managing the corresponding DDL.
|
||||
|
||||
Note - this macro is likely to be deprecated in future in favour of a
|
||||
dedicated "datamodel" folder (prefix mddl)
|
||||
|
||||
Any corresponding data would go in a seperate repo, to avoid this one
|
||||
ballooning in size!
|
||||
|
||||
Example usage:
|
||||
|
||||
%mp_coretable(LOCKTABLE,libds=work.locktable)
|
||||
|
||||
@param [in] table_ref The type of table to create. Example values:
|
||||
@li FILTER_DETAIL - For storing detailed filter values. Used by
|
||||
mp_filterstore.sas.
|
||||
@li FILTER_SUMMARY - For storing summary filter values. Used by
|
||||
mp_filterstore.sas.
|
||||
@li LOCKANYTABLE - For "locking" tables prior to multipass loads. Used by
|
||||
mp_lockanytable.sas
|
||||
@li MAXKEYTABLE - For storing the maximum retained key information. Used
|
||||
by mp_retainedkey.sas
|
||||
@li DIFFTABLE
|
||||
@li FILTER_DETAIL
|
||||
@li FILTER_SUMMARY
|
||||
@li LOCKANYTABLE
|
||||
@li MAXKEYTABLE
|
||||
@param [in] libds= (0) The library.dataset reference used to create the table.
|
||||
If not provided, then the DDL is simply printed to the log.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mddl_dc_difftable.sas
|
||||
@li mddl_dc_filterdetail.sas
|
||||
@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
|
||||
@li mp_lockanytable.sas
|
||||
@li mp_retainedkey.sas
|
||||
@li mp_storediffs.sas
|
||||
@li mp_stackdiffs.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -34,59 +47,27 @@
|
||||
%macro mp_coretable(table_ref,libds=0
|
||||
)/*/STORE SOURCE*/;
|
||||
%local outds ;
|
||||
%let outds=%sysfunc(ifc(&libds=0,_data_,&libds));
|
||||
%let outds=%sysfunc(ifc(&libds=0,%mf_getuniquename(),&libds));
|
||||
proc sql;
|
||||
%if &table_ref=LOCKTABLE %then %do;
|
||||
create table &outds(
|
||||
lock_lib char(8),
|
||||
lock_ds char(32),
|
||||
lock_status_cd char(10) not null,
|
||||
lock_user_nm char(100) not null ,
|
||||
lock_ref char(200),
|
||||
lock_pid char(10),
|
||||
lock_start_dttm num format=E8601DT26.6,
|
||||
lock_end_dttm num format=E8601DT26.6,
|
||||
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds));
|
||||
%if &table_ref=DIFFTABLE %then %do;
|
||||
%mddl_dc_difftable(libds=&outds)
|
||||
%end;
|
||||
%else %if &table_ref=LOCKTABLE %then %do;
|
||||
%mddl_dc_locktable(libds=&outds)
|
||||
%end;
|
||||
%else %if &table_ref=FILTER_SUMMARY %then %do;
|
||||
create table &outds(
|
||||
filter_rk num not null,
|
||||
filter_hash char(32) not null,
|
||||
filter_table char(41) not null,
|
||||
processed_dttm num not null format=E8601DT26.6,
|
||||
constraint pk_mpe_filteranytable
|
||||
primary key(filter_rk));
|
||||
%mddl_dc_filtersummary(libds=&outds)
|
||||
%end;
|
||||
%else %if &table_ref=FILTER_DETAIL %then %do;
|
||||
create table &outds(
|
||||
filter_hash char(32) not null,
|
||||
filter_line num not null,
|
||||
group_logic char(3) not null,
|
||||
subgroup_logic char(3) not null,
|
||||
subgroup_id num not null,
|
||||
variable_nm varchar(32) not null,
|
||||
operator_nm varchar(12) not null,
|
||||
raw_value varchar(4000) not null,
|
||||
processed_dttm num not null format=E8601DT26.6,
|
||||
constraint pk_mpe_filteranytable
|
||||
primary key(filter_hash,filter_line));
|
||||
%mddl_dc_filterdetail(libds=&outds)
|
||||
%end;
|
||||
%else %if &table_ref=MAXKEYTABLE %then %do;
|
||||
create table &outds(
|
||||
keytable varchar(41) label='Base table in libref.dataset format',
|
||||
keycolumn char(32) format=$32.
|
||||
label='The Retained key field containing the key values.',
|
||||
max_key num label=
|
||||
'Integer representing current max RK or SK value in the KEYTABLE',
|
||||
processed_dttm num format=E8601DT26.6
|
||||
label='Datetime this value was last updated',
|
||||
constraint pk_mpe_maxkeyvalues
|
||||
primary key(keytable));
|
||||
%mddl_dc_maxkeytable(libds=&outds)
|
||||
%end;
|
||||
|
||||
|
||||
%if &libds=0 %then %do;
|
||||
proc sql;
|
||||
describe table &syslast;
|
||||
drop table &syslast;
|
||||
%end;
|
||||
%mend mp_coretable;
|
||||
%mend mp_coretable;
|
||||
|
||||
@@ -18,11 +18,14 @@
|
||||
%mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES)
|
||||
%mp_createconstraints(inds=work.constraints,outds=created,execute=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.
|
||||
@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.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
<h4> Related Files </h4>
|
||||
@li mp_getconstraints.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -30,7 +33,7 @@
|
||||
**/
|
||||
|
||||
%macro mp_createconstraints(inds=mp_getconstraints
|
||||
,outds=mp_createconstraints
|
||||
,outds=work.mp_createconstraints
|
||||
,execute=NO
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
@@ -64,4 +67,4 @@ data &outds;
|
||||
output;
|
||||
run;
|
||||
|
||||
%mend mp_createconstraints;
|
||||
%mend mp_createconstraints;
|
||||
|
||||
@@ -1,47 +1,13 @@
|
||||
/**
|
||||
@file mp_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
|
||||
@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.
|
||||
|
||||
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,code=ft15f001,replace=YES)
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getplatform.sas
|
||||
@li mm_createwebservice.sas
|
||||
@li mv_createwebservice.sas
|
||||
@li mx_createwebservice.sas
|
||||
|
||||
@param path= The full folder path where the service will be created
|
||||
@param name= Service name. Avoid spaces.
|
||||
@param desc= The description of the service (optional)
|
||||
@param precode= Space separated list of filerefs, pointing to the code that
|
||||
needs to be attached to the beginning of the service (optional)
|
||||
@param code= Space seperated fileref(s) of the actual code to be added
|
||||
@param replace= select YES to replace any existing service in that location
|
||||
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
@@ -51,33 +17,16 @@ Usage:
|
||||
,code=ft15f001
|
||||
,desc=This service was created by the mp_createwebservice macro
|
||||
,replace=YES
|
||||
,mdebug=0
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%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
|
||||
%mx_createwebservice(path=&path
|
||||
,name=&name
|
||||
,code=&code
|
||||
,precode=&precode
|
||||
,code=&code
|
||||
,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;
|
||||
|
||||
@@ -19,11 +19,12 @@
|
||||
%mp_csv2ds(inref=mycsv,outds=myds,baseds=sashelp.class)
|
||||
|
||||
|
||||
@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.
|
||||
@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.
|
||||
Is used to determine types, lengths, and any informats.
|
||||
|
||||
@version 9.2
|
||||
@@ -49,10 +50,6 @@
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(the BASEDS variable must be provided)
|
||||
)
|
||||
%mp_abort(iftrue=( &baseds=0 )
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(the BASEDS variable must be provided)
|
||||
)
|
||||
%mp_abort(iftrue=( %mf_existds(&baseds)=0 )
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(the BASEDS dataset (&baseds) needs to be assigned, and to exist)
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
%mp_getconstraints(lib=work,ds=example,outds=work.constraints)
|
||||
%mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=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.
|
||||
@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.
|
||||
|
||||
|
||||
@version 9.2
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
%mp_deletefolder(&rootdir)
|
||||
|
||||
@param path Unquoted path to the folder to delete.
|
||||
@param [in] folder Unquoted path to the folder to delete.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquename.sas
|
||||
@@ -51,6 +51,7 @@
|
||||
data _null_;
|
||||
set work.&tempds end=last;
|
||||
length fref $8;
|
||||
fref='';
|
||||
rc=filename(fref,filepath);
|
||||
rc=fdelete(fref);
|
||||
if rc then do;
|
||||
|
||||
52
base/mp_dictionary.sas
Normal file
52
base/mp_dictionary.sas
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
@file mp_dictionary.sas
|
||||
@brief Creates a portal (libref) into the SQL Dictionary Views
|
||||
@details Provide a libref and the macro will create a series of views against
|
||||
each view in the special PROC SQL dictionary libref.
|
||||
|
||||
This is useful if you would like to visualise (navigate) the views in a SAS
|
||||
client such as Base SAS, Enterprise Guide, or Studio (or [Data Controller](
|
||||
https://datacontroller.io)).
|
||||
|
||||
It works by extracting the dictionary.dictionaries view into
|
||||
YOURLIB.dictionaries, then uses that to create a YOURLIB.{viewName} for every
|
||||
other dictionary.view, eg:
|
||||
|
||||
proc sql;
|
||||
create view YOURLIB.columns as select * from dictionary.columns;
|
||||
|
||||
Usage:
|
||||
|
||||
libname demo "/lib/directory";
|
||||
%mp_dictionary(lib=demo)
|
||||
|
||||
Or, to just create them in WORK:
|
||||
|
||||
%mp_dictionary()
|
||||
|
||||
If you'd just like to browse the dictionary data model, you can also check
|
||||
out [this article](https://rawsas.com/dictionary-of-dictionaries/).
|
||||
|
||||

|
||||
|
||||
@param [in] lib= (WORK) The libref in which to create the views
|
||||
|
||||
<h4> Related Files </h4>
|
||||
@li mp_dictionary.test.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_dictionary(lib=WORK)/*/STORE SOURCE*/;
|
||||
%local list i mem;
|
||||
proc sql noprint;
|
||||
create view &lib..dictionaries as select * from dictionary.dictionaries;
|
||||
select distinct memname into: list separated by ' ' from &lib..dictionaries;
|
||||
%do i=1 %to %sysfunc(countw(&list,%str( )));
|
||||
%let mem=%scan(&list,&i,%str( ));
|
||||
create view &lib..&mem as select * from dictionary.&mem;
|
||||
%end;
|
||||
quit;
|
||||
%mend mp_dictionary;
|
||||
@@ -6,8 +6,7 @@
|
||||
Credit for the rename approach:
|
||||
https://communities.sas.com/t5/SAS-Programming/SAS-Function-to-convert-string-to-Legal-SAS-Name/m-p/27375/highlight/true#M5003
|
||||
|
||||
|
||||
usage:
|
||||
Usage:
|
||||
|
||||
%mp_dirlist(path=/some/location, outds=myTable, maxdepth=MAX)
|
||||
|
||||
@@ -23,12 +22,15 @@
|
||||
X CMD) do please raise an issue!
|
||||
|
||||
|
||||
@param [in] path= for which to return contents
|
||||
@param [in] fref= Provide a DISK engine fileref as an alternative to PATH
|
||||
@param [in] path= (%sysfunc(pathname(work))) Path for which to return contents
|
||||
@param [in] fref= (0) Provide a DISK engine fileref as an alternative to PATH
|
||||
@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 [out] outds= the output dataset to create
|
||||
@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
|
||||
valid in a SAS name (v7) are simply stripped, and the table is transposed
|
||||
@@ -49,19 +51,22 @@
|
||||
- OS SPECIFIC variables, if <code>getattrs=</code> is used.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_existds.sas
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_wordsinstr1butnotstr2.sas
|
||||
@li mp_dropmembers.sas
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_dirlist.test.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_dirlist(path=%sysfunc(pathname(work))
|
||||
, 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*/;
|
||||
@@ -80,8 +85,9 @@ data;run;
|
||||
data &out_ds(compress=no
|
||||
keep=file_or_folder filepath filename ext msg directory level
|
||||
);
|
||||
length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80
|
||||
ext $20 msg $200;
|
||||
length directory filepath $2000 fref fref2 $8 file_or_folder $6 filename $255
|
||||
ext $20 msg $200 foption $16;
|
||||
if _n_=1 then call missing(of _all_);
|
||||
retain level &level;
|
||||
%if &fref=0 %then %do;
|
||||
rc = filename(fref, "&path");
|
||||
@@ -92,13 +98,19 @@ data &out_ds(compress=no
|
||||
%end;
|
||||
if rc = 0 then do;
|
||||
did = dopen(fref);
|
||||
directory=dinfo(did,'Directory');
|
||||
if did=0 then do;
|
||||
putlog "NOTE: This directory is empty - " directory;
|
||||
putlog "NOTE: This directory is empty, or does not exist - &path";
|
||||
msg=sysmsg();
|
||||
put _all_;
|
||||
put (_all_)(=);
|
||||
stop;
|
||||
end;
|
||||
/* attribute is OS-dependent - could be "Directory" or "Directory Name" */
|
||||
numopts=doptnum(did);
|
||||
do i=1 to numopts;
|
||||
foption=doptname(did,i);
|
||||
if foption=:'Directory' then i=numopts;
|
||||
end;
|
||||
directory=dinfo(did,foption);
|
||||
rc = filename(fref);
|
||||
end;
|
||||
else do;
|
||||
@@ -136,6 +148,15 @@ 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;
|
||||
|
||||
@@ -143,6 +164,7 @@ 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;
|
||||
@@ -193,15 +215,38 @@ data &out_ds;
|
||||
set &out_ds(where=(filepath ne ''));
|
||||
run;
|
||||
|
||||
/* update main table */
|
||||
proc append base=&outds data=&out_ds;
|
||||
run;
|
||||
/**
|
||||
* The above transpose can mean that some updates create additional columns.
|
||||
* This necessitates the occasional use of datastep over proc append.
|
||||
*/
|
||||
%if %mf_existds(&outds) %then %do;
|
||||
%local basevars appvars newvars;
|
||||
%let basevars=%mf_getvarlist(&outds);
|
||||
%let appvars=%mf_getvarlist(&out_ds);
|
||||
%let newvars=%length(%mf_wordsinstr1butnotstr2(Str1=&appvars,Str2=&basevars));
|
||||
%if &newvars>0 %then %do;
|
||||
data &outds;
|
||||
set &outds &out_ds;
|
||||
run;
|
||||
%end;
|
||||
%else %do;
|
||||
proc append base=&outds data=&out_ds force nowarn;
|
||||
run;
|
||||
%end;
|
||||
%end;
|
||||
%else %do;
|
||||
proc append base=&outds data=&out_ds;
|
||||
run;
|
||||
%end;
|
||||
|
||||
/* recursive call */
|
||||
%if &maxdepth>&level or &maxdepth=MAX %then %do;
|
||||
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))");
|
||||
@@ -214,4 +259,4 @@ run;
|
||||
proc sql;
|
||||
drop table &out_ds;
|
||||
|
||||
%mend mp_dirlist;
|
||||
%mend mp_dirlist;
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
|
||||
%mp_distinctfmtvalues(libds=sashelp.class,var=age,outvar=age,outds=test)
|
||||
|
||||
@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)
|
||||
@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
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
Usage:
|
||||
|
||||
%mp_ds2cards(base_ds=sashelp.class
|
||||
%mp_ds2cards(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,9 +48,10 @@
|
||||
|
||||
@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
|
||||
@@ -139,8 +140,9 @@ create table datalines1 as
|
||||
/**
|
||||
Due to long decimals cannot use best. format
|
||||
So - use bestd. format and then use character functions to strip trailing
|
||||
zeros, if NOT an integer!!
|
||||
resolved code = ifc(int(VARIABLE)=VARIABLE
|
||||
zeros, if NOT an integer or missing!! Cannot use int() as it upsets
|
||||
note2err when there are missings.
|
||||
resolved code = ifc( mod(coalesce(VARIABLE,0),1)=0
|
||||
,put(VARIABLE,best32.)
|
||||
,substrn(put(VARIABLE,bestd32.),1
|
||||
,findc(put(VARIABLE,bestd32.),'0','TBK')));
|
||||
@@ -151,7 +153,7 @@ data datalines_2;
|
||||
set datalines1 (where=(upcase(name) not in
|
||||
('PROCESSED_DTTM','VALID_FROM_DTTM','VALID_TO_DTTM')));
|
||||
if type='num' then dataline=
|
||||
cats('ifc(int(',name,')=',name,'
|
||||
cats('ifc(mod(coalesce(',name,',0),1)=0
|
||||
,put(',name,',best32.-l)
|
||||
,substrn(put(',name,',bestd32.-l),1
|
||||
,findc(put(',name,',bestd32.-l),"0","TBK")))');
|
||||
@@ -218,7 +220,8 @@ data _null_;
|
||||
put ' @file';
|
||||
put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset";
|
||||
put " @details Generated by %nrstr(%%)mp_ds2cards()";
|
||||
put " Available on github.com/sasjs/core";
|
||||
put " Source: https://github.com/sasjs/core";
|
||||
put ' @cond ';
|
||||
put '**/';
|
||||
put "data &tgt_ds &indexes;";
|
||||
put "attrib ";
|
||||
@@ -251,6 +254,7 @@ 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;
|
||||
@@ -263,6 +267,7 @@ data _null_;
|
||||
if __lastobs then do;
|
||||
put ';;;;';
|
||||
put 'run;';
|
||||
put '/** @endcond **/';
|
||||
stop;
|
||||
end;
|
||||
run;
|
||||
@@ -282,4 +287,5 @@ quit;
|
||||
%put NOTE-;%put NOTE-;
|
||||
%put NOTE- %sysfunc(dequote(&cards_file.));
|
||||
%put NOTE-;%put NOTE-;
|
||||
%mend mp_ds2cards;
|
||||
%mend mp_ds2cards;
|
||||
/** @endcond **/
|
||||
|
||||
@@ -1,23 +1,82 @@
|
||||
/**
|
||||
@file
|
||||
@brief Export a dataset to a CSV file
|
||||
@details Export to a file or a fileref
|
||||
@brief Export a dataset to a CSV file WITH leading blanks
|
||||
@details Export a dataset to a file or fileref, retaining leading blanks.
|
||||
|
||||
When using SASJS headerformat, the input statement is provided in the first
|
||||
row of the CSV.
|
||||
|
||||
Usage:
|
||||
|
||||
%mp_ds2csv(sashelp.class,outref="%sysfunc(pathname(work))/file.csv")
|
||||
|
||||
@param ds The dataset to be exported
|
||||
@param outfile= The output filename - should be quoted.
|
||||
@param outref= The output fileref (takes precedence if provided)
|
||||
@param outencoding= The output encoding to use (unquoted)
|
||||
filename example temp;
|
||||
%mp_ds2csv(sashelp.air,outref=example,headerformat=SASJS)
|
||||
data; infile example; input;put _infile_; if _n_>5 then stop;run;
|
||||
|
||||
data _null_;
|
||||
infile example;
|
||||
input;
|
||||
call symputx('stmnt',_infile_);
|
||||
stop;
|
||||
run;
|
||||
data work.want;
|
||||
infile example dsd firstobs=2;
|
||||
input &stmnt;
|
||||
run;
|
||||
|
||||
Why use mp_ds2csv over, say, proc export?
|
||||
|
||||
1. Ability to retain leading blanks (this is a major one)
|
||||
2. Control the header format
|
||||
3. Simple one-liner
|
||||
|
||||
@param [in] ds The dataset to be exported
|
||||
@param [in] dlm= (COMMA) The delimeter to apply. For SASJS, will always be
|
||||
COMMA. Supported values:
|
||||
@li COMMA
|
||||
@li SEMICOLON
|
||||
@param [in] headerformat= (LABEL) The format to use for the header section.
|
||||
Valid values:
|
||||
@li LABEL - Use the variable label (or name, if blank)
|
||||
@li NAME - Use the variable name
|
||||
@li SASJS - Used to create sasjs-formatted input CSVs, eg for use in
|
||||
mp_testservice.sas. This format will supply an input statement in the
|
||||
first row, making ingestion by datastep a breeze. Special misisng values
|
||||
will be prefixed with a period (eg `.A`) to enable ingestion on both SAS 9
|
||||
and Viya. Dates / Datetimes etc are identified by the format type (lookup
|
||||
with mcf_getfmttype.sas) and converted to human readable formats (not
|
||||
numbers).
|
||||
@param [out] outfile= The output filename - should be quoted.
|
||||
@param [out] outref= (0) The output fileref (takes precedence if provided)
|
||||
@param [in] outencoding= (0) The (quoted) output encoding to use, eg `"UTF-8"`
|
||||
@param [in] termstr= (CRLF) The line seperator to use. For SASJS, will
|
||||
always be CRLF. Valid values:
|
||||
@li CRLF
|
||||
@li LF
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mcf_getfmttype.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_getvarformat.sas
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_getvartype.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe (credit mjsq)
|
||||
**/
|
||||
|
||||
%macro mp_ds2csv(ds, outref=0, outfile=, outencoding=0
|
||||
%macro mp_ds2csv(ds
|
||||
,dlm=COMMA
|
||||
,outref=0
|
||||
,outfile=
|
||||
,outencoding=0
|
||||
,headerformat=LABEL
|
||||
,termstr=CRLF
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local outloc delim i varlist var vcnt vat dsv vcom vmiss fmttype vfmt;
|
||||
|
||||
%if not %sysfunc(exist(&ds)) %then %do;
|
||||
%put %str(WARN)ING: &ds does not exist;
|
||||
%return;
|
||||
@@ -26,33 +85,137 @@
|
||||
%if %index(&ds,.)=0 %then %let ds=WORK.&ds;
|
||||
|
||||
%if &outencoding=0 %then %let outencoding=;
|
||||
%else %let outencoding=encoding="&outencoding";
|
||||
%else %let outencoding=encoding=&outencoding;
|
||||
|
||||
%local outloc;
|
||||
%if &outref=0 %then %let outloc=&outfile;
|
||||
%else %let outloc=&outref;
|
||||
|
||||
%if &headerformat=SASJS %then %do;
|
||||
%let delim=",";
|
||||
%let termstr=CRLF;
|
||||
%mcf_getfmttype(wrap=YES)
|
||||
%end;
|
||||
%else %if &dlm=COMMA %then %let delim=",";
|
||||
%else %let delim=";";
|
||||
|
||||
/* credit to mjsq - https://stackoverflow.com/a/55642267 */
|
||||
|
||||
/* first get headers */
|
||||
data _null_;
|
||||
file &outloc dlm=',' dsd &outencoding lrecl=32767;
|
||||
length header $ 2000;
|
||||
file &outloc &outencoding lrecl=32767 termstr=&termstr;
|
||||
length header $ 2000 varnm vfmt $32 dlm $1 fmttype $8;
|
||||
call missing(of _all_);
|
||||
dsid=open("&ds.","i");
|
||||
num=attrn(dsid,"nvars");
|
||||
dlm=&delim;
|
||||
do i=1 to num;
|
||||
header = trim(left(coalescec(varlabel(dsid,i),varname(dsid,i))));
|
||||
varnm=upcase(varname(dsid,i));
|
||||
if i=num then dlm='';
|
||||
%if &headerformat=NAME %then %do;
|
||||
header=cats(varnm,dlm);
|
||||
%end;
|
||||
%else %if &headerformat=LABEL %then %do;
|
||||
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,'.');
|
||||
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;
|
||||
%else %do;
|
||||
%put &sysmacroname: Invalid headerformat value (&headerformat);
|
||||
%return;
|
||||
%end;
|
||||
put header @;
|
||||
end;
|
||||
rc=close(dsid);
|
||||
run;
|
||||
|
||||
%let varlist=%mf_getvarlist(&ds);
|
||||
%let vcnt=%sysfunc(countw(&varlist));
|
||||
|
||||
/**
|
||||
* The $quote modifier (without a width) will take the length from the variable
|
||||
* and increase by two. However this will lead to truncation where the value
|
||||
* contains double quotes (which are doubled up). To get around this, scan the
|
||||
* data to see the max number of double quotes, so that the appropriate width
|
||||
* can be applied in the subsequent step.
|
||||
*/
|
||||
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_);
|
||||
retain &dsv1 0;
|
||||
&dsv2=length(&var)+countc(&var,'"');
|
||||
if &dsv2>&dsv1 then &dsv1=&dsv2;
|
||||
if last then call symputx(
|
||||
"vlen&i"
|
||||
/* should be no shorter than varlen, and no longer than 32767 */
|
||||
,cats('$quote',min(&dsv1+2,32767),'.')
|
||||
,'l'
|
||||
);
|
||||
%end;
|
||||
%end;
|
||||
|
||||
%let vat=@;
|
||||
%let vcom=&delim;
|
||||
%let vmiss=%mf_getuniquename(prefix=csvcol3_);
|
||||
/* next, export data */
|
||||
data _null_;
|
||||
set &ds.;
|
||||
file &outloc mod dlm=',' dsd &outencoding lrecl=32767;
|
||||
put (_all_) (+0);
|
||||
file &outloc mod dlm=&delim dsd &outencoding lrecl=32767 termstr=&termstr;
|
||||
if _n_=1 then &vmiss=' ';
|
||||
%do i=1 %to &vcnt;
|
||||
%let var=%scan(&varlist,&i);
|
||||
%if &i=&vcnt %then %do;
|
||||
%let vat=;
|
||||
%let vcom=;
|
||||
%end;
|
||||
%if %mf_getvartype(&ds,&var)=N %then %do;
|
||||
%if &headerformat = SASJS %then %do;
|
||||
%let vcom=&delim;
|
||||
%let fmttype=%sysfunc(mcf_getfmttype(%mf_getvarformat(&ds,&var)0));
|
||||
%if &fmttype=DATE %then %let vfmt=DATE9.;
|
||||
%else %if &fmttype=DATETIME %then %let vfmt=E8601DT26.6;
|
||||
%else %if &fmttype=TIME %then %let vfmt=TIME12.;
|
||||
%else %do;
|
||||
%let vfmt=;
|
||||
%let vcom=;
|
||||
%end;
|
||||
%end;
|
||||
%else %let vcom=;
|
||||
|
||||
/* must use period - in order to work in both 9.4 and Viya 3.5 */
|
||||
if missing(&var) and &var ne %sysfunc(getoption(MISSING)) then do;
|
||||
&vmiss=cats('.',&var);
|
||||
put &vmiss &vat;
|
||||
end;
|
||||
else put &var &vfmt &vcom &vat;
|
||||
|
||||
%end;
|
||||
%else %do;
|
||||
%if &i ne &vcnt %then %let vcom=&delim;
|
||||
put &var &&vlen&i &vcom &vat;
|
||||
%end;
|
||||
%end;
|
||||
run;
|
||||
|
||||
|
||||
%mend mp_ds2csv;
|
||||
42
base/mp_ds2ddl.sas
Normal file
42
base/mp_ds2ddl.sas
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
@file
|
||||
@brief Fetches DDL for a specific table
|
||||
@details Uses mp_getddl under the hood
|
||||
|
||||
@param [in] libds library.dataset to create ddl for
|
||||
@param [in] fref= (getddl) the fileref to which to _append_ the DDL. If it
|
||||
does not exist, it will be created.
|
||||
@param [in] flavour= (SAS) The type of DDL to create. Options:
|
||||
@li SAS
|
||||
@li TSQL
|
||||
|
||||
@param [in]showlog= (NO) Set to YES to show the DDL in the log
|
||||
@param [in] schema= () Choose a preferred schema name (default is to use
|
||||
actual schema, else libref)
|
||||
@param [in] applydttm= (NO) For non SAS DDL, choose if columns are created with
|
||||
native datetime2 format or regular decimal type
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_getddl.sas
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_ds2ddl(libds,fref=getddl,flavour=SAS,showlog=YES,schema=
|
||||
,applydttm=NO
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local libref;
|
||||
%let libds=%upcase(&libds);
|
||||
%let libref=%scan(&libds,1,.);
|
||||
%if &libref=&libds %then %let libds=WORK.&libds;
|
||||
|
||||
%mp_getddl(%scan(&libds,1,.)
|
||||
,%scan(&libds,2,.)
|
||||
,fref=&fref
|
||||
,flavour=SAS
|
||||
,showlog=&showlog
|
||||
,schema=&schema
|
||||
,applydttm=&applydttm
|
||||
)
|
||||
|
||||
%mend mp_ds2ddl;
|
||||
@@ -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
|
||||
|
||||
@@ -57,6 +57,11 @@
|
||||
%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;
|
||||
@@ -87,12 +92,13 @@ data _null_;
|
||||
run;
|
||||
|
||||
%if %upcase(&showlog)=YES %then %do;
|
||||
options ps=max;
|
||||
options ps=max lrecl=max;
|
||||
data _null_;
|
||||
infile &outref;
|
||||
if _n_=1 then putlog "# &libds" /;
|
||||
input;
|
||||
putlog _infile_;
|
||||
run;
|
||||
%end;
|
||||
|
||||
%mend mp_ds2md;
|
||||
%mend mp_ds2md;
|
||||
|
||||
120
base/mp_ds2squeeze.sas
Normal file
120
base/mp_ds2squeeze.sas
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
@file
|
||||
@brief Create a smaller version of a dataset, without data loss
|
||||
@details This macro will scan the input dataset and create a new one, that
|
||||
has the minimum variable lengths needed to store the data without data loss.
|
||||
|
||||
Inspiration was taken from [How to Reduce the Disk Space Required by a
|
||||
SAS® Data Set](https://www.lexjansen.com/nesug/nesug06/io/io18.pdf) by
|
||||
Selvaratnam Sridharma. The end of the referenced paper presents a macro named
|
||||
"squeeze", hence the nomenclature.
|
||||
|
||||
Usage:
|
||||
|
||||
data big;
|
||||
length my big $32000;
|
||||
do i=1 to 1e4;
|
||||
my=repeat('oh my',100);
|
||||
big='dawg';
|
||||
special=._;
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
|
||||
%mp_ds2squeeze(work.big,outds=work.smaller)
|
||||
|
||||
The following will also be printed to the log (exact values may differ
|
||||
depending on your OS and COMPRESS settings):
|
||||
|
||||
> MP_DS2SQUEEZE: work.big was 625MB
|
||||
|
||||
> MP_DS2SQUEEZE: work.smaller is 5MB
|
||||
|
||||
@param [in] libds The library.dataset to be squeezed
|
||||
@param [out] outds= (work.mp_ds2squeeze) The squeezed dataset to create
|
||||
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getfilesize.sas
|
||||
@li mf_getuniquefileref.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mp_getmaxvarlengths.sas
|
||||
|
||||
<h4> Related Programs </h4>
|
||||
@li mp_ds2squeeze.test.sas
|
||||
|
||||
@version 9.3
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_ds2squeeze(
|
||||
libds,
|
||||
outds=work.mp_ds2squeeze,
|
||||
mdebug=0
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dbg source;
|
||||
%if &mdebug=1 %then %do;
|
||||
%put &sysmacroname entry vars:;
|
||||
%put _local_;
|
||||
%end;
|
||||
%else %do;
|
||||
%let dbg=*;
|
||||
%let source=/source2;
|
||||
%end;
|
||||
|
||||
%local optval ds fref startsize;
|
||||
%let ds=%mf_getuniquename();
|
||||
%let fref=%mf_getuniquefileref();
|
||||
%let startsize=%mf_getfilesize(libds=&libds,format=yes);
|
||||
|
||||
%mp_getmaxvarlengths(&libds,outds=&ds)
|
||||
|
||||
data _null_;
|
||||
set &ds end=last;
|
||||
file &fref;
|
||||
/* grab the types */
|
||||
retain dsid;
|
||||
if _n_=1 then dsid=open("&libds",'is');
|
||||
if dsid le 0 then do;
|
||||
msg=sysmsg();
|
||||
put msg=;
|
||||
stop;
|
||||
end;
|
||||
type=vartype(dsid,varnum(dsid, name));
|
||||
if last then rc=close(dsid);
|
||||
/* write out the length statement */
|
||||
if _n_=1 then put 'length ';
|
||||
length len $6;
|
||||
if type='C' then do;
|
||||
if maxlen=0 then len='$1';
|
||||
else len=cats('$',maxlen);
|
||||
end;
|
||||
else do;
|
||||
if maxlen=0 then len='3';
|
||||
else len=cats(maxlen);
|
||||
end;
|
||||
put ' ' name ' ' len;
|
||||
if last then put ';';
|
||||
run;
|
||||
|
||||
/* configure varlenchk - as we are explicitly shortening the variables */
|
||||
%let optval=%sysfunc(getoption(varlenchk));
|
||||
options varlenchk=NOWARN;
|
||||
|
||||
data &outds;
|
||||
%inc &fref &source;
|
||||
set &libds;
|
||||
run;
|
||||
|
||||
options varlenchk=&optval;
|
||||
|
||||
%if &mdebug=0 %then %do;
|
||||
proc sql;
|
||||
drop table &ds;
|
||||
filename &fref clear;
|
||||
%end;
|
||||
|
||||
%put &sysmacroname: &libds was &startsize;
|
||||
%put &sysmacroname: &outds is %mf_getfilesize(libds=&outds,format=yes);
|
||||
|
||||
%mend mp_ds2squeeze;
|
||||
110
base/mp_dsmeta.sas
Normal file
110
base/mp_dsmeta.sas
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
@file
|
||||
@brief Export dataset metadata to a single output table
|
||||
@details Exports the dataset attributes and enginehost information, then
|
||||
converts the datasets into a single output table in the following format:
|
||||
|
||||
|ODS_TABLE:$10.|NAME:$100.|VALUE:$1000.|
|
||||
|---|---|---|
|
||||
|`ATTRIBUTES `|`Data Set Name `|`SASHELP.CLASS `|
|
||||
|`ATTRIBUTES `|`Observations `|`19 `|
|
||||
|`ATTRIBUTES `|`Member Type `|`DATA `|
|
||||
|`ATTRIBUTES `|`Variables `|`5 `|
|
||||
|`ATTRIBUTES `|`Engine `|`V9 `|
|
||||
|`ATTRIBUTES `|`Indexes `|`0 `|
|
||||
|`ATTRIBUTES `|`Created `|`06/08/2020 00:59:14 `|
|
||||
|`ATTRIBUTES `|`Observation Length `|`40 `|
|
||||
|`ATTRIBUTES `|`Last Modified `|`06/08/2020 00:59:14 `|
|
||||
|`ATTRIBUTES `|`Deleted Observations `|`0 `|
|
||||
|`ATTRIBUTES `|`Protection `|`. `|
|
||||
|`ATTRIBUTES `|`Compressed `|`NO `|
|
||||
|`ATTRIBUTES `|`Data Set Type `|`. `|
|
||||
|`ATTRIBUTES `|`Sorted `|`NO `|
|
||||
|`ATTRIBUTES `|`Label `|`Student Data `|
|
||||
|`ATTRIBUTES `|`Data Representation `|`SOLARIS_X86_64, LINUX_X86_64, ALPHA_TRU64, LINUX_IA64 `|
|
||||
|`ATTRIBUTES `|`Encoding `|`us-ascii ASCII (ANSI) `|
|
||||
|`ENGINEHOST `|`Data Set Page Size `|`65536 `|
|
||||
|`ENGINEHOST `|`Number of Data Set Pages `|`1 `|
|
||||
|`ENGINEHOST `|`First Data Page `|`1 `|
|
||||
|`ENGINEHOST `|`Max Obs per Page `|`1632 `|
|
||||
|`ENGINEHOST `|`Obs in First Data Page `|`19 `|
|
||||
|`ENGINEHOST `|`Number of Data Set Repairs `|`0 `|
|
||||
|`ENGINEHOST `|`Filename `|`/opt/sas/sas9/SASHome/SASFoundation/9.4/sashelp/class.sas7bdat `|
|
||||
|`ENGINEHOST `|`Release Created `|`9.0401M7 `|
|
||||
|`ENGINEHOST `|`Host Created `|`Linux `|
|
||||
|`ENGINEHOST `|`Inode Number `|`28314616 `|
|
||||
|`ENGINEHOST `|`Access Permission `|`rw-r--r-- `|
|
||||
|`ENGINEHOST `|`Owner Name `|`sas `|
|
||||
|`ENGINEHOST `|`File Size `|`128KB `|
|
||||
|`ENGINEHOST `|`File Size (bytes) `|`131072 `|
|
||||
|
||||
Example usage:
|
||||
|
||||
%mp_dsmeta(sashelp.class,outds=work.mymeta)
|
||||
proc print data=work.mymeta;
|
||||
run;
|
||||
|
||||
For more details on creating datasets from PROC CONTENTS check out this
|
||||
excellent [paper](
|
||||
https://support.sas.com/resources/papers/proceedings14/1549-2014.pdf) by
|
||||
[Louise Hadden](https://www.linkedin.com/in/louisehadden/).
|
||||
|
||||
@param [in] libds The library.dataset to export the metadata for
|
||||
@param [out] outds= (work.dsmeta) The output table to contain the metadata
|
||||
|
||||
<h4> Related Files </h4>
|
||||
@li mp_dsmeta.test.sas
|
||||
@li mp_getcols.sas
|
||||
@li mp_getdbml.sas
|
||||
@li mp_getddl.sas
|
||||
@li mp_getformats.sas
|
||||
@li mp_getpk.sas
|
||||
@li mp_guesspk.sas
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_dsmeta(libds,outds=work.dsmeta);
|
||||
|
||||
%local ds1 ds2;
|
||||
data;run; %let ds1=&syslast;
|
||||
data;run; %let ds2=&syslast;
|
||||
|
||||
/* setup the ODS capture */
|
||||
ods output attributes=&ds1 enginehost=&ds2;
|
||||
|
||||
/* export the metadata */
|
||||
proc contents data=&libds;
|
||||
run;
|
||||
|
||||
/* load it into a single table */
|
||||
data &outds (keep=ods_table name value);
|
||||
length ods_table $10 name label2 label1 label $100
|
||||
value cvalue cvalue1 cvalue2 $1000
|
||||
nvalue nvalue1 nvalue2 8;
|
||||
if _n_=1 then call missing (of _all_);
|
||||
* putlog (_all_)(=);
|
||||
set &ds1 (in=atrs) &ds2 (in=eng);
|
||||
if atrs then do;
|
||||
ods_table='ATTRIBUTES';
|
||||
name=coalescec(label1,label);
|
||||
value=coalescec(cvalue1,cvalue,put(coalesce(nvalue1,nvalue),best.));
|
||||
output;
|
||||
if label2 ne '' then do;
|
||||
name=label2;
|
||||
value=coalescec(cvalue2,put(nvalue2,best.));
|
||||
output;
|
||||
end;
|
||||
end;
|
||||
else if eng then do;
|
||||
ods_table='ENGINEHOST';
|
||||
name=coalescec(label1,label);
|
||||
value=coalescec(cvalue1,cvalue,put(coalesce(nvalue1,nvalue),best.));
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
|
||||
proc sql;
|
||||
drop table &ds1, &ds2;
|
||||
|
||||
%mend mp_dsmeta;
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
SYSCC to 1008 if bad records are found, and call mp_abort.sas for a
|
||||
graceful service exit (configurable).
|
||||
|
||||
Used for dynamic filtering in [Data Controller for SAS®](https://datacontroller.io).
|
||||
Used for dynamic filtering in [Data Controller for SAS®](
|
||||
https://datacontroller.io).
|
||||
|
||||
Usage:
|
||||
|
||||
@@ -36,10 +37,12 @@
|
||||
@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= 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.
|
||||
@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.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_abort.sas
|
||||
@@ -83,15 +86,53 @@
|
||||
/**
|
||||
* 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;
|
||||
length reason_cd $4032;
|
||||
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;
|
||||
|
||||
/* closed list checks */
|
||||
if GROUP_LOGIC not in ('AND','OR') then do;
|
||||
@@ -109,44 +150,112 @@ data &outds;
|
||||
output;
|
||||
end;
|
||||
if mod(SUBGROUP_ID,1) ne 0 then do;
|
||||
REASON_CD='SUBGROUP_ID should be integer, not '!!left(subgroup_id);
|
||||
REASON_CD='SUBGROUP_ID should be integer, not '!!cats(subgroup_id);
|
||||
putlog REASON_CD= SUBGROUP_ID=;
|
||||
call symputx('reason_cd',reason_cd,'l');
|
||||
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
|
||||
('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS')
|
||||
('=','>','<','<=','>=','NE','GE','LE','BETWEEN','IN','NOT IN','CONTAINS')
|
||||
then do;
|
||||
REASON_CD='Invalid OPERATOR_NM: '!!left(OPERATOR_NM);
|
||||
REASON_CD='Invalid OPERATOR_NM: '!!cats(OPERATOR_NM);
|
||||
putlog REASON_CD= OPERATOR_NM=;
|
||||
call symputx('reason_cd',reason_cd,'l');
|
||||
call symputx('nobs',_n_,'l');
|
||||
output;
|
||||
end;
|
||||
|
||||
/* special logic */
|
||||
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 ')'
|
||||
/* 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;
|
||||
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;
|
||||
/* 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;
|
||||
end;
|
||||
else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));
|
||||
end;
|
||||
else raw_value1=raw_value;
|
||||
|
||||
@@ -163,14 +272,42 @@ 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 REASON_CD= raw_value= raw_value1= raw_value2= raw_value3=;
|
||||
putlog (_all_)(=);
|
||||
call symputx('reason_cd',reason_cd,'l');
|
||||
call symputx('nobs',_n_,'l');
|
||||
output;
|
||||
end;
|
||||
|
||||
endstep:
|
||||
if last then rc=close(dsid);
|
||||
run;
|
||||
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@
|
||||
> )
|
||||
|
||||
@param [in] inds The input table with query values
|
||||
@param [out] outref= The output fileref to contain the filter clause. Will
|
||||
be created (or replaced).
|
||||
@param [out] outref= (filter) The output fileref to contain the filter clause.
|
||||
Will be created (or replaced).
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_filtercheck.sas
|
||||
@@ -84,6 +84,9 @@ 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;
|
||||
|
||||
@@ -8,8 +8,13 @@
|
||||
https://sasapps.io)). This macro is also used in [Data Controller for SAS](
|
||||
https://datacontroller.io).
|
||||
|
||||
A more recent feature of this macro is the ability to support filter queries
|
||||
on Format Catalogs. This is achieved by adding a `-FC` suffix to the `libds`
|
||||
parameter - where the "ds" in this case is the catalog name.
|
||||
|
||||
@param [in] libds= The target dataset to be filtered (lib should be assigned)
|
||||
|
||||
@param [in] libds= The target dataset to be filtered (lib should be assigned).
|
||||
If filtering a format catalog, add the following suffix: `-FC`.
|
||||
@param [in] queryds= (WORK.FILTERQUERY) The temporary input query dataset to
|
||||
be validated. Has the following format:
|
||||
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767|
|
||||
@@ -39,12 +44,14 @@
|
||||
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= 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
|
||||
@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
|
||||
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mddl_sas_cntlout.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_getvalue.sas
|
||||
@li mf_islibds.sas
|
||||
@@ -78,7 +85,10 @@
|
||||
%put &sysmacroname entry vars:;
|
||||
%put _local_;
|
||||
|
||||
%local ds1 ds2 ds3 ds4 filter_hash;
|
||||
%local ds0 ds1 ds2 ds3 ds4 filter_hash orig_libds;
|
||||
%let libds=%upcase(&libds);
|
||||
%let orig_libds=&libds;
|
||||
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=mp_filterstore
|
||||
,msg=%str(syscc=&syscc on macro entry)
|
||||
@@ -96,12 +106,27 @@
|
||||
,msg=%str(Invalid lock_table value: &lock_table)
|
||||
)
|
||||
|
||||
/* validate query */
|
||||
/**
|
||||
* validate query
|
||||
* use format catalog export, if a format
|
||||
*/
|
||||
%if "%substr(&libds,%length(&libds)-2,3)"="-FC" %then %do;
|
||||
%let libds=%scan(&libds,1,-); /* chop off -FC extension */
|
||||
%let ds0=%mf_getuniquename(prefix=fmtds_);
|
||||
%let libds=&ds0;
|
||||
/*
|
||||
There is no need to export the entire format catalog here - the validations
|
||||
are done against the data model, not the data values. So we can simply
|
||||
hardcode the structure based on the cntlout dataset.
|
||||
*/
|
||||
%mddl_sas_cntlout(libds=&ds0)
|
||||
|
||||
%end;
|
||||
%mp_filtercheck(&queryds,targetds=&libds,abort=YES)
|
||||
|
||||
/* hash the result */
|
||||
%let ds1=%mf_getuniquename(prefix=hashds);
|
||||
%mp_hashdataset(&queryds,outds=&ds1,salt=&libds)
|
||||
%mp_hashdataset(&queryds,outds=&ds1,salt=&orig_libds)
|
||||
%let filter_hash=%upcase(%mf_getvalue(&ds1,hashkey));
|
||||
%if &mdebug=1 %then %do;
|
||||
data _null_;
|
||||
@@ -132,7 +157,7 @@ run;
|
||||
%let ds3=%mf_getuniquename(prefix=filtersum);
|
||||
data work.&ds3;
|
||||
if 0 then set &filter_summary;
|
||||
filter_table=symget('libds');
|
||||
filter_table="&orig_libds";
|
||||
filter_hash="&filter_hash";
|
||||
PROCESSED_DTTM=%sysfunc(datetime());
|
||||
output;
|
||||
|
||||
@@ -96,8 +96,7 @@ filename &fref1 clear;
|
||||
run;
|
||||
%mp_abort(
|
||||
mac=&sysmacroname,
|
||||
msg=%str(Filter validation issues. ERR=%superq(SYSERRORTEXT)
|
||||
, WARN=%superq(SYSWARNINGTEXT) )
|
||||
msg=%str(Filter validation issues.)
|
||||
)
|
||||
%end;
|
||||
%let syscc=1008;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user