Skip to content

Instantly share code, notes, and snippets.

@maksimr
Created October 25, 2024 14:57
Show Gist options
  • Save maksimr/29168a6a7a4b8a48ff58cd6d3d8a1bc2 to your computer and use it in GitHub Desktop.
Save maksimr/29168a6a7a4b8a48ff58cd6d3d8a1bc2 to your computer and use it in GitHub Desktop.
// @ts-check
main();
/**
* @returns {Promise<void>}
*/
async function main() {
/**@type {{version?: string; output?: string; help?: boolean}}*/
const options = {};
for (const arg of process.argv.slice(2)) {
if (!arg.startsWith('--')) {
continue;
}
const i = process.argv.indexOf(arg);
const rawKey = arg.slice(2);
const [key, value] = arg.includes('=') ? rawKey.split('=') : [rawKey, process.argv[i + 1] ?? true];
options[key] = value;
}
const defaultVersion = '4.13.2';
if (options.help) {
console.log('Usage:');
console.log(` [--version=<version>] Version of ANTLR (default: ${defaultVersion})`);
console.log(' [--output=<path>.jar] Output path for the jar file');
return
}
const version = options.version || defaultVersion;
const antlrJarPath = options.output || require('path').join(require('os').tmpdir(), `antlr-${version}-complete.jar`);
const antlrJarUrl = `https://www.antlr.org/download/antlr-${version}-complete.jar`
await downloadAntlr({
downloadUrl: antlrJarUrl,
outputFilePath: antlrJarPath
});
console.log(antlrJarPath)
}
/**
* @param {{downloadUrl: string; outputFilePath: string; force?: Boolean}} param0
* @returns {Promise<string>}
*/
async function downloadAntlr({
downloadUrl,
outputFilePath,
force = false,
}) {
if (!force && await exists(outputFilePath)) {
return outputFilePath;
}
await downloadFile(downloadUrl)
.then(async (response) => {
const path = require('path')
const installDir = path.dirname(outputFilePath);
await require('fs/promises').mkdir(installDir, { recursive: true });
return new Promise((resolve, reject) => {
const progress = {
packageSize: parseInt(response.headers['content-length'] || '', 10),
dots: 0,
downloadedBytes: 0,
downloadPercentage: 0
};
console.log(`(${Math.ceil(progress.packageSize / 1024)} KB) `);
response.on('data', data => {
progress.downloadedBytes += data.length;
// Update status bar item with percentage
if (progress.packageSize > 0) {
let newPercentage = Math.ceil(100 * (progress.downloadedBytes / progress.packageSize));
if (newPercentage !== progress.downloadPercentage) {
progress.downloadPercentage = newPercentage;
if (progress.downloadPercentage === 100) {
process.stdout.write('\n');
}
}
// Update dots after package name in output console
let newDots = Math.ceil(progress.downloadPercentage / 5);
if (newDots > progress.dots) {
process.stdout.write('.'.repeat(newDots - progress.dots));
progress.dots = newDots;
}
}
});
const { createWriteStream } = require('fs');
const tmpFile = createWriteStream(outputFilePath);
response.on('end', () => {
resolve(tmpFile.path);
});
response.on('error', (/**@type {Error & {code?: string}}*/ err) => {
reject(new Error(`Response error: ${err.code || 'NONE'}`));
});
// Begin piping data from the response to the package file
response.pipe(tmpFile, { end: false });
});
});
return outputFilePath;
}
/**
* @param {string} fileUrl
* @returns {Promise<import('http').IncomingMessage>}
*/
function downloadFile(fileUrl) {
const options = getHttpClientOptions(fileUrl, undefined, false);
const httpClient = fileUrl.startsWith('http:') ?
require('http').request :
require('https').request;
return new Promise((resolve, reject) => {
const request = httpClient(options, response => {
if (response.statusCode === 301 || response.statusCode === 302) {
if (!response.headers.location) {
return reject(new Error('Redirect status code, but no location header'));
}
// Redirect - download from new location
return resolve(
downloadFile(
response.headers.location
)
);
}
if (response.statusCode !== 200) {
// Download failed - print error message
return reject(new Error(response.statusCode?.toString()));
}
resolve(response);
});
request.on('error', (/**@type {Error & {code?: string}}*/ error) => {
reject(error);
});
request.end();
});
}
/**
* @param {string} urlStr
* @param {string} [proxy]
* @param {boolean} [strictSSL]
* @param {string} [authorization]
*/
function getHttpClientOptions(urlStr, proxy, strictSSL, authorization) {
const url = require('url').parse(urlStr);
const agent = getProxyAgent(urlStr, proxy, strictSSL);
/**@type {import('http').RequestOptions & import('https').RequestOptions}*/
const options = {
host: url.hostname,
path: url.path,
agent: agent
};
if (url.protocol === 'https:') {
options.rejectUnauthorized = isBoolean(strictSSL) ? strictSSL : true;
}
if (authorization) {
options.headers = Object.assign(options.headers || {}, { 'Proxy-Authorization': authorization });
}
return options;
}
/**
* @param {string} requestUrl
* @param {string} [proxy]
* @param {boolean} [strictSSL]
* @returns {any}
*/
function getProxyAgent(requestUrl, proxy, strictSSL) {
const proxyURL = proxy || getSystemProxyURL(requestUrl);
if (!proxyURL) {
return undefined;
}
if (!/^https?:/.test(requestUrl)) {
return undefined;
}
const ProxyAgent = requestUrl.startsWith('http:') ?
require('http-proxy-agent').HttpProxyAgent :
require('https-proxy-agent').HttpsProxyAgent
return new ProxyAgent(proxyURL, {
rejectUnauthorized: isBoolean(strictSSL) ? strictSSL : true
});
/**
* @param {string} requestUrl
* @returns {string | undefined}
*/
function getSystemProxyURL(requestUrl) {
if (requestUrl.startsWith('http:')) {
return process.env.HTTP_PROXY || process.env.http_proxy || undefined;
} else if (requestUrl.startsWith('https:')) {
return process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || undefined;
}
return undefined;
}
}
/**
* @param {any} obj
* @returns {obj is boolean}
*/
function isBoolean(obj) {
return obj === true || obj === false;
}
async function exists(filePath) {
const fs = require('fs');
return await fs.promises.access(filePath, fs.constants.F_OK)
.then(() => true)
.catch(() => false);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment