2019-07-25 06:17:19 +00:00
|
|
|
import { skip, isSpace, removeComments } from './util';
|
|
|
|
import { logger } from '../../logger';
|
|
|
|
import { PackageFile, PackageDependency } from '../common';
|
2020-03-01 07:01:12 +00:00
|
|
|
import * as datasourceGithubTags from '../../datasource/github-tags';
|
2019-05-01 08:39:40 +00:00
|
|
|
|
2019-11-23 20:44:55 +00:00
|
|
|
function parseSha256(idx: number, content: string): string | null {
|
|
|
|
let i = idx;
|
|
|
|
i += 'sha256'.length;
|
|
|
|
i = skip(i, content, c => {
|
|
|
|
return isSpace(c);
|
|
|
|
});
|
|
|
|
if (content[i] !== '"' && content[i] !== "'") {
|
2019-05-01 08:39:40 +00:00
|
|
|
return null;
|
|
|
|
}
|
2019-11-23 20:44:55 +00:00
|
|
|
i += 1;
|
2020-02-24 11:27:10 +00:00
|
|
|
const j = skip(i, content, c => {
|
2019-11-23 20:44:55 +00:00
|
|
|
return c !== '"' && c !== "'";
|
|
|
|
});
|
|
|
|
const sha256 = content.slice(i, j);
|
|
|
|
return sha256;
|
2019-05-01 08:39:40 +00:00
|
|
|
}
|
|
|
|
|
2019-08-22 15:42:35 +00:00
|
|
|
function extractSha256(content: string): string | null {
|
2019-05-01 08:39:40 +00:00
|
|
|
const sha256RegExp = /(^|\s)sha256(\s)/;
|
|
|
|
let i = content.search(sha256RegExp);
|
|
|
|
if (isSpace(content[i])) {
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
return parseSha256(i, content);
|
|
|
|
}
|
|
|
|
|
2019-11-23 20:44:55 +00:00
|
|
|
function parseUrl(idx: number, content: string): string | null {
|
2019-05-01 08:39:40 +00:00
|
|
|
let i = idx;
|
2019-11-23 20:44:55 +00:00
|
|
|
i += 'url'.length;
|
2019-05-01 08:39:40 +00:00
|
|
|
i = skip(i, content, c => {
|
|
|
|
return isSpace(c);
|
|
|
|
});
|
2019-11-23 20:44:55 +00:00
|
|
|
const chr = content[i];
|
|
|
|
if (chr !== '"' && chr !== "'") {
|
2019-05-01 08:39:40 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
i += 1;
|
2020-02-24 11:27:10 +00:00
|
|
|
const j = skip(i, content, c => {
|
2019-11-23 20:44:55 +00:00
|
|
|
return c !== '"' && c !== "'" && !isSpace(c);
|
2019-05-01 08:39:40 +00:00
|
|
|
});
|
2019-11-23 20:44:55 +00:00
|
|
|
const url = content.slice(i, j);
|
|
|
|
return url;
|
2019-05-01 08:39:40 +00:00
|
|
|
}
|
|
|
|
|
2019-08-22 15:42:35 +00:00
|
|
|
function extractUrl(content: string): string | null {
|
2019-05-01 08:39:40 +00:00
|
|
|
const urlRegExp = /(^|\s)url(\s)/;
|
|
|
|
let i = content.search(urlRegExp);
|
|
|
|
// content.search() returns -1 if not found
|
|
|
|
if (i === -1) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (isSpace(content[i])) {
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
return parseUrl(i, content);
|
|
|
|
}
|
|
|
|
|
2019-08-22 15:42:35 +00:00
|
|
|
export interface UrlPathParsedResult {
|
|
|
|
currentValue: string;
|
|
|
|
ownerName: string;
|
|
|
|
repoName: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function parseUrlPath(urlStr: string): UrlPathParsedResult | null {
|
2019-05-01 08:39:40 +00:00
|
|
|
if (!urlStr) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
const url = new URL(urlStr);
|
|
|
|
if (url.hostname !== 'github.com') {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
let s = url.pathname.split('/');
|
|
|
|
s = s.filter(val => val);
|
|
|
|
const ownerName = s[0];
|
|
|
|
const repoName = s[1];
|
2019-07-25 06:17:19 +00:00
|
|
|
let currentValue: string;
|
2019-05-01 08:39:40 +00:00
|
|
|
if (s[2] === 'archive') {
|
|
|
|
currentValue = s[3];
|
|
|
|
const targz = currentValue.slice(
|
|
|
|
currentValue.length - 7,
|
|
|
|
currentValue.length
|
|
|
|
);
|
|
|
|
if (targz === '.tar.gz') {
|
|
|
|
currentValue = currentValue.substring(0, currentValue.length - 7);
|
|
|
|
}
|
|
|
|
} else if (s[2] === 'releases' && s[3] === 'download') {
|
|
|
|
currentValue = s[4];
|
|
|
|
}
|
|
|
|
if (!currentValue) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return { currentValue, ownerName, repoName };
|
|
|
|
} catch (_) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This function parses the "class className < Formula" header
|
|
|
|
and returns the className and index of the character just after the header */
|
2019-08-22 15:42:35 +00:00
|
|
|
function parseClassHeader(idx: number, content: string): string | null {
|
2019-05-01 08:39:40 +00:00
|
|
|
let i = idx;
|
|
|
|
i += 'class'.length;
|
|
|
|
i = skip(i, content, c => {
|
|
|
|
return isSpace(c);
|
|
|
|
});
|
|
|
|
// Skip all non space and non '<' characters
|
|
|
|
let j = skip(i, content, c => {
|
|
|
|
return !isSpace(c) && c !== '<';
|
|
|
|
});
|
|
|
|
const className = content.slice(i, j);
|
|
|
|
i = j;
|
|
|
|
// Skip spaces
|
|
|
|
i = skip(i, content, c => {
|
|
|
|
return isSpace(c);
|
|
|
|
});
|
|
|
|
if (content[i] === '<') {
|
|
|
|
i += 1;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
} // Skip spaces
|
|
|
|
i = skip(i, content, c => {
|
|
|
|
return isSpace(c);
|
|
|
|
});
|
|
|
|
// Skip non-spaces
|
|
|
|
j = skip(i, content, c => {
|
|
|
|
return !isSpace(c);
|
|
|
|
});
|
|
|
|
if (content.slice(i, j) !== 'Formula') {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return className;
|
|
|
|
}
|
2019-11-23 20:44:55 +00:00
|
|
|
|
|
|
|
function extractClassName(content: string): string | null {
|
|
|
|
const classRegExp = /(^|\s)class\s/;
|
|
|
|
let i = content.search(classRegExp);
|
|
|
|
if (isSpace(content[i])) {
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
return parseClassHeader(i, content);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Maybe check if quotes/double-quotes are balanced
|
|
|
|
export function extractPackageFile(content: string): PackageFile | null {
|
|
|
|
logger.trace('extractPackageFile()');
|
|
|
|
/*
|
|
|
|
1. match "class className < Formula"
|
|
|
|
2. extract className
|
|
|
|
3. extract url field (get depName from url)
|
|
|
|
4. extract sha256 field
|
|
|
|
*/
|
|
|
|
const cleanContent = removeComments(content);
|
|
|
|
const className = extractClassName(cleanContent);
|
|
|
|
if (!className) {
|
|
|
|
logger.debug('Invalid class definition');
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const url = extractUrl(cleanContent);
|
|
|
|
if (!url) {
|
|
|
|
logger.debug('Invalid URL field');
|
|
|
|
}
|
|
|
|
const urlPathResult = parseUrlPath(url);
|
|
|
|
let skipReason: string;
|
|
|
|
let currentValue: string = null;
|
|
|
|
let ownerName: string = null;
|
|
|
|
let repoName: string = null;
|
|
|
|
if (urlPathResult) {
|
|
|
|
currentValue = urlPathResult.currentValue;
|
|
|
|
ownerName = urlPathResult.ownerName;
|
|
|
|
repoName = urlPathResult.repoName;
|
|
|
|
} else {
|
|
|
|
logger.debug('Error: Unsupported URL field');
|
|
|
|
skipReason = 'unsupported-url';
|
|
|
|
}
|
|
|
|
const sha256 = extractSha256(cleanContent);
|
|
|
|
if (!sha256 || sha256.length !== 64) {
|
|
|
|
logger.debug('Error: Invalid sha256 field');
|
|
|
|
skipReason = 'invalid-sha256';
|
|
|
|
}
|
|
|
|
const dep: PackageDependency = {
|
|
|
|
depName: `${ownerName}/${repoName}`,
|
|
|
|
managerData: { ownerName, repoName, sha256, url },
|
|
|
|
currentValue,
|
2020-03-01 07:01:12 +00:00
|
|
|
datasource: datasourceGithubTags.id,
|
2019-11-23 20:44:55 +00:00
|
|
|
};
|
|
|
|
if (skipReason) {
|
|
|
|
dep.skipReason = skipReason;
|
|
|
|
if (skipReason === 'unsupported-url') {
|
|
|
|
dep.depName = className;
|
|
|
|
dep.datasource = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const deps = [dep];
|
|
|
|
return { deps };
|
|
|
|
}
|