-
Notifications
You must be signed in to change notification settings - Fork 106
/
Copy pathTools.ts
322 lines (273 loc) Β· 9.27 KB
/
Tools.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
import Crypto from 'crypto';
import { readFileSync } from "fs";
import path from "path";
import vscode from "vscode";
import { t } from "../locale";
import { IBMiMessage, IBMiMessages, QsysPath } from '../typings';
import { API, GitExtension } from "./import/git";
export namespace Tools {
export class SqlError extends Error {
public sqlstate: string = "0";
constructor(message: string) {
super(message);
}
}
export interface DB2Headers {
name: string
from: number
length: number
}
export interface DB2Row extends Record<string, string | number | null> {
}
/**
* Parse standard out for `/usr/bin/db2`
* @param output /usr/bin/db2's output
* @returns rows
*/
export function db2Parse(output: string): DB2Row[] {
let gotHeaders = false;
let figuredLengths = false;
let iiErrorMessage = false;
const data = output.split(`\n`).filter(line => {
const trimmed = line.trim();
return trimmed !== `DB2>` &&
!trimmed.startsWith(`DB20`) && // Notice messages
!/COMMAND .+ COMPLETED WITH EXIT STATUS \d+/.test(trimmed) && // @CL command execution output
trimmed !== `?>`;
});
if (!data[data.length - 1]) {
data.pop();
}
let headers: DB2Headers[];
let SQLSTATE: string;
const rows: DB2Row[] = [];
data.forEach((line, index) => {
const trimmed = line.trim();
if (trimmed.length === 0 && iiErrorMessage) iiErrorMessage = false;
if (trimmed.length === 0 || index === data.length - 1) return;
if (trimmed === `**** CLI ERROR *****`) {
iiErrorMessage = true;
if (data.length > index + 3) {
SQLSTATE = data[index + 1].trim();
if (SQLSTATE.includes(`:`)) {
SQLSTATE = SQLSTATE.split(`:`)[1].trim();
}
if (!SQLSTATE.startsWith(`01`)) {
let sqlError = new SqlError(`${data[index + 3]} (${SQLSTATE})`);
sqlError.sqlstate = SQLSTATE;
throw sqlError;
}
}
return;
}
if (iiErrorMessage) return;
if (gotHeaders === false) {
headers = line.split(` `)
.filter(header => header.length > 0)
.map(header => {
return {
name: header,
from: 0,
length: 0,
};
});
gotHeaders = true;
} else if (figuredLengths === false) {
let base = 0;
line.split(` `).forEach((header, index) => {
headers[index].from = base;
headers[index].length = header.length;
base += header.length + 1;
});
figuredLengths = true;
} else {
let row: DB2Row = {};
headers.forEach(header => {
const strValue = line.substring(header.from, header.from + header.length).trimEnd();
let realValue: string | number | null = strValue;
// is value a number?
if (strValue.startsWith(` `)) {
const asNumber = Number(strValue.trim());
if (!isNaN(asNumber)) {
realValue = asNumber;
}
} else if (strValue === `-`) {
realValue = null; //null?
}
row[header.name] = realValue;
});
rows.push(row);
}
});
return rows;
}
export function makeid(length: number = 8) {
let text = `O_`;
const possible =
`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`;
for (let i = 0; i < length; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
/**
* Build the IFS path string to a member
* @param library
* @param object
* @param member
* @param iasp Optional: an iASP name
*/
export function qualifyPath(library: string, object: string, member: string, iasp?: string, sanitise?: boolean) {
const libraryPath = library === `QSYS` ? `QSYS.LIB` : `QSYS.LIB/${Tools.sanitizeLibraryNames([library]).join(``)}.LIB`;
const memberPath = `${object}.FILE/${member}.MBR`
const memberSubpath = sanitise ? Tools.escapePath(memberPath) : memberPath;
const result = (iasp && iasp.length > 0 ? `/${iasp}` : ``) + `/${libraryPath}/${memberSubpath}`;
return result;
}
/**
* Unqualify member path from root
*/
export function unqualifyPath(memberPath: string) {
const pathInfo = path.posix.parse(memberPath);
let splitPath = pathInfo.dir.split(path.posix.sep);
// Remove use of `QSYS.LIB` two libraries in the path aren't value
const isInQsys = splitPath.filter(part => part.endsWith(`.LIB`)).length === 2;
if (isInQsys) {
splitPath = splitPath.filter(part => part !== `QSYS.LIB`);
}
const correctedDir = splitPath.map(part => {
const partInfo = path.posix.parse(part);
if ([`.FILE`, `.LIB`].includes(partInfo.ext)) {
return partInfo.name
} else {
return part
}
})
.join(path.posix.sep);
return path.posix.join(correctedDir, pathInfo.base);
}
/**
* @param Path
* @returns the escaped path
*/
export function escapePath(Path: string): string {
const path = Path.replace(/'|"|\$|\\| /g, matched => `\\`.concat(matched));
return path;
}
let gitLookedUp: boolean;
let gitAPI: API | undefined;
export function getGitAPI(): API | undefined {
if (!gitLookedUp) {
try {
gitAPI = vscode.extensions.getExtension<GitExtension>(`vscode.git`)?.exports.getAPI(1);
}
catch (error) {
console.log(`Git extension issue.`, error);
}
finally {
gitLookedUp = true;
}
}
return gitAPI;
}
export function distinct(value: any, index: number, array: any[]) {
return array.indexOf(value) === index;
}
export function md5Hash(file: vscode.Uri): string {
const bytes = readFileSync(file.fsPath);
return Crypto.createHash("md5")
.update(bytes)
.digest("hex")
.toLowerCase();
}
export function capitalize(text: string) {
return text.charAt(0).toUpperCase() + text.slice(1);
}
export function sanitizeLibraryNames(libraries: string[]): string[] {
return libraries
.map(library => {
// Escape any $ signs
library = library.replace(/\$/g, `\\$`);
// Quote libraries starting with #
return library.startsWith(`#`) ? `"${library}"` : library;
});
}
export function parseMessages(output: string): IBMiMessages {
const messages = output.split("\n").map(line => ({
id: line.substring(0, line.indexOf(':')).trim(),
text: line.substring(line.indexOf(':') + 1).trim()
}) as IBMiMessage);
return {
messages,
findId: id => messages.find(m => m.id === id)
}
}
export function parseQSysPath(path: string): QsysPath {
const parts = path.split('/');
if (parts.length > 3) {
return {
asp: parts[0],
library: parts[1],
name: parts[2]
}
}
else {
return {
library: parts[0],
name: parts[1]
}
}
}
/**
* We do this to find previously opened files with the same path, but different case OR readonly flags.
* Without this, it's possible for the same document to be opened twice simply due to the readonly flag.
*/
export function findExistingDocumentUri(uri: vscode.Uri) {
const baseUriString = uriStringWithoutFragment(uri);
const possibleDoc = vscode.workspace.textDocuments.find(document => uriStringWithoutFragment(document.uri) === baseUriString);
return possibleDoc?.uri || uri;
}
/**
* We convert member to lowercase as members are case insensitive.
*/
function uriStringWithoutFragment(uri: vscode.Uri) {
// To lowercase because the URI path is case-insensitive
const baseUri = uri.scheme + `:` + uri.path;
const isCaseSensitive = (uri.scheme === `streamfile` && /^\/QOpenSys\//i.test(uri.path));
return (isCaseSensitive ? baseUri : baseUri.toLowerCase());
}
/**
* Fixes an SQL statement to make it compatible with db2 CLI program QZDFMDB2.
* - Changes `@clCommand` statements into Call `QSYS2.QCMDEX('clCommand')` procedure calls
* - Makes sure each comment (`--`) starts on a new line
* @param statement the statement to fix
* @returns statement compatible with QZDFMDB2
*/
export function fixSQL(statement: string) {
return statement.split("\n").map(line => {
if (line.startsWith('@')) {
//- Escape all '
//- Remove any trailing ;
//- Put the command in a Call QSYS2.QCMDEXC statement
line = `Call QSYS2.QCMDEXC('${line.substring(1, line.endsWith(";") ? line.length - 1 : undefined).replaceAll("'", "''")}');`;
}
//Make each comment start on a new line
return line.replaceAll("--", "\n--");
}
).join("\n");
}
export function generateTooltipHtmlTable(header: string, rows: Record<string, any>) {
return `<table>`
.concat(`${header ? `<thead>${header}</thead>` : ``}`)
.concat(`${Object.entries(rows).map(([key, value]) => `<tr><td>${t(key)}:</td><td> ${value}</td></tr>`).join(``)}`)
.concat(`</table>`);
}
export function fixWindowsPath(path:string){
if (process.platform === `win32` && path[0] === `/`) {
//Issue with getFile not working propertly on Windows
//when there was a / at the start.
return path.substring(1);
} else {
return path;
}
}
}