456 lines
18 KiB
JavaScript
456 lines
18 KiB
JavaScript
// ----------------------------------------------------------------------------
|
|
// Usage:
|
|
//
|
|
// npm run export-exchanges
|
|
// ----------------------------------------------------------------------------
|
|
|
|
"use strict";
|
|
|
|
const fs = require ('fs')
|
|
, countries = require ('./countries')
|
|
, asTable = require ('as-table')
|
|
, execSync = require ('child_process').execSync
|
|
, log = require ('ololog').unlimited
|
|
, ansi = require ('ansicolor').nice
|
|
, { keys, values, entries } = Object
|
|
, { replaceInFile } = require ('./fs.js')
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
function cloneGitHubWiki (gitWikiPath) {
|
|
|
|
if (!fs.existsSync (gitWikiPath)) {
|
|
log.bright.cyan ('Cloning ccxt.wiki...')
|
|
execSync ('git clone https://github.com/ccxt/ccxt.wiki.git ' + gitWikiPath)
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
function logExportExchanges (filename, regex, replacement) {
|
|
log.bright.cyan ('Exporting exchanges →', filename.yellow)
|
|
replaceInFile (filename, regex, replacement)
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
function getIncludedExchangeIds () {
|
|
|
|
const includedIds = fs.readFileSync ('exchanges.cfg')
|
|
.toString () // Buffer → String
|
|
.split ('\n') // String → Array
|
|
.map (line => line.split ('#')[0].trim ()) // trim comments
|
|
.filter (exchange => exchange); // filter empty lines
|
|
|
|
const isIncluded = (id) => ((includedIds.length === 0) || includedIds.includes (id))
|
|
|
|
const ids = fs.readdirSync ('./js/')
|
|
.filter (file => file.match (/[a-zA-Z0-9_-]+.js$/))
|
|
.map (file => file.slice (0, -3))
|
|
.filter (isIncluded);
|
|
|
|
return ids
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
function exportExchanges (replacements) {
|
|
|
|
log.bright.yellow ('Exporting exchanges...')
|
|
|
|
replacements.forEach (({ file, regex, replacement }) => {
|
|
logExportExchanges (file, regex, replacement)
|
|
})
|
|
|
|
log.bright.green ('Base sources updated successfully.')
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
function createExchanges (ids) {
|
|
|
|
const ccxt = require ('../ccxt.js')
|
|
|
|
const createExchange = (id) => {
|
|
ccxt[id].prototype.checkRequiredDependencies = () => {} // suppress it
|
|
return new (ccxt)[id] ()
|
|
}
|
|
|
|
return ccxt.indexBy (ids.map (createExchange), 'id')
|
|
}
|
|
|
|
// // ----------------------------------------------------------------------------
|
|
// // strategically placed exactly here (we can require it AFTER the export)
|
|
// const ccxt = require ('../ccxt.js')
|
|
// // ----------------------------------------------------------------------------
|
|
// // create exchanges
|
|
// const createExchange = (id) => {
|
|
// ccxt[id].prototype.checkRequiredDependencies = () => {} // suppress it
|
|
// return new (ccxt)[id] ()
|
|
// }
|
|
// const exchanges = ccxt.indexBy (ids.map (createExchange), 'id')
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// TODO: REWRITE THIS ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
|
|
|
function exportSupportedAndCertifiedExchanges (exchanges, { allExchangesPaths, certifiedExchangesPaths, exchangesByCountriesPaths, proExchangesPaths }) {
|
|
|
|
// ............................................................................
|
|
// markup constants and helper functions
|
|
|
|
const countryName = (code) => countries[code] || code
|
|
|
|
const ccxtCertifiedBadge = '[](https://github.com/ccxt/ccxt/wiki/Certification)'
|
|
const ccxtProBadge = '[](https://ccxt.pro)'
|
|
const logoHeading = ' '.repeat (7) + 'logo' + ' '.repeat (7)
|
|
const tableHeadings = [ logoHeading, 'id', 'name', 'ver', 'doc', 'certified', 'pro' ]
|
|
const exchangesByCountryHeading = [ 'country / region', ... tableHeadings.slice (0, 5) ]
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// list all supported exchanges
|
|
|
|
const exchangesNotListedInDocs = [ 'hitbtc2' ]
|
|
|
|
function makeTableData (exchanges) {
|
|
return (
|
|
values (exchanges)
|
|
.filter (exchange => !exchangesNotListedInDocs.includes (exchange.id))
|
|
.map (exchange => {
|
|
let logo = exchange.urls['logo']
|
|
let website = Array.isArray (exchange.urls.www) ? exchange.urls.www[0] : exchange.urls.www
|
|
let url = exchange.urls.referral || website
|
|
let doc = Array.isArray (exchange.urls.doc) ? exchange.urls.doc[0] : exchange.urls.doc
|
|
let version = exchange.version ? exchange.version : '\*'
|
|
let matches = version.match (/[^0-9]*([0-9].*)/)
|
|
if (matches) {
|
|
version = matches[1];
|
|
}
|
|
return [
|
|
'[](' + url + ')',
|
|
exchange.id,
|
|
'[' + exchange.name + '](' + url + ')',
|
|
version,
|
|
'[API](' + doc + ')',
|
|
exchange.certified ? ccxtCertifiedBadge : '',
|
|
exchange.pro ? ccxtProBadge : '',
|
|
]
|
|
})
|
|
)
|
|
}
|
|
|
|
|
|
function makeTable (jsonArray) {
|
|
let table = asTable.configure ({ 'delimiter': ' | ' }) (jsonArray)
|
|
let lines = table.split ("\n")
|
|
lines.splice (1,0, lines[0].replace (/[^\|]/g, '-'))
|
|
let headerLine = lines[1].split ('|')
|
|
headerLine[3] = ':' + headerLine[3].slice (1, headerLine[3].length - 1) + ':'
|
|
headerLine[4] = ':' + headerLine[4].slice (1, headerLine[4].length - 1) + ':'
|
|
lines[1] = headerLine.join ('|')
|
|
return lines.map (line => '|' + line + '|').join ("\n")
|
|
}
|
|
|
|
if (allExchangesPaths) {
|
|
|
|
const tableData = makeTableData (exchanges)
|
|
const numExchanges = tableData.length
|
|
// prepend the table header
|
|
tableData.splice (0, 0, tableHeadings)
|
|
const exchangesTable = makeTable (tableData)
|
|
const beginning = "The CCXT library currently supports the following "
|
|
const ending = " cryptocurrency exchange markets and trading APIs:\n\n"
|
|
const totalString = beginning + numExchanges + ending
|
|
const allExchanges = totalString + exchangesTable + "$1"
|
|
const allExchangesRegex = new RegExp ("[^\n]+[\n]{2}\\|[^`]+\\|([\n][\n]|[\n]$|$)", 'm')
|
|
|
|
for (const path of allExchangesPaths) {
|
|
logExportExchanges (path, allExchangesRegex, allExchanges)
|
|
}
|
|
|
|
// logExportExchanges ('README.md', allExchangesRegex, allExchanges)
|
|
// logExportExchanges (wikiPath + '/Manual.md', allExchangesRegex, allExchanges)
|
|
// logExportExchanges (wikiPath + '/Exchange-Markets.md', allExchangesRegex, allExchanges)
|
|
}
|
|
|
|
if (proExchangesPaths) {
|
|
const pro = values (exchanges).filter (exchange => exchange.pro)
|
|
const tableData = makeTableData (pro)
|
|
const numExchanges = tableData.length
|
|
// prepend the table header
|
|
tableData.splice (0, 0, tableHeadings)
|
|
const exchangesTable = makeTable (tableData)
|
|
const beginning = "The CCXT Pro library currently supports the following "
|
|
const ending = " cryptocurrency exchange markets and WebSocket trading APIs:\n\n"
|
|
const totalString = beginning + numExchanges + ending
|
|
const proExchanges = totalString + exchangesTable + "$1"
|
|
const proExchangesRegex = new RegExp ("[^\n]+[\n]{2}\\|[^`]+\\|([\n][\n]|[\n]$|$)", 'm')
|
|
|
|
for (const path of proExchangesPaths) {
|
|
logExportExchanges (path, proExchangesRegex, proExchanges)
|
|
}
|
|
}
|
|
|
|
if (certifiedExchangesPaths) {
|
|
const certified = values (exchanges).filter (exchange => exchange.certified)
|
|
const tableData = makeTableData (certified)
|
|
// prepend the table header
|
|
tableData.splice (0, 0, tableHeadings)
|
|
const certifiedExchangesRegex = new RegExp ("^(## Certified Cryptocurrency Exchanges\n{3})(?:\\|.+\\|$\n)+", 'm')
|
|
const certifiedExchangesTable = makeTable (tableData)
|
|
const certifiedExchanges = '$1' + certifiedExchangesTable + "\n"
|
|
|
|
for (const path of certifiedExchangesPaths) {
|
|
logExportExchanges (path, certifiedExchangesRegex, certifiedExchanges)
|
|
}
|
|
|
|
// logExportExchanges ('README.md', certifiedExchangesRegex, certifiedExchanges)
|
|
}
|
|
|
|
if (exchangesByCountriesPaths) {
|
|
let exchangesByCountries = []
|
|
keys (countries).forEach (code => {
|
|
let country = countries[code]
|
|
let result = []
|
|
keys (exchanges).forEach (id => {
|
|
let exchange = exchanges[id]
|
|
let logo = exchange.urls['logo']
|
|
let website = Array.isArray (exchange.urls.www) ? exchange.urls.www[0] : exchange.urls.www
|
|
let url = exchange.urls.referral || website
|
|
let doc = Array.isArray (exchange.urls.doc) ? exchange.urls.doc[0] : exchange.urls.doc
|
|
let version = exchange.version ? exchange.version : '\*'
|
|
let matches = version.match (/[^0-9]*([0-9].*)/)
|
|
if (matches)
|
|
version = matches[1];
|
|
let shouldInclude = false
|
|
if (Array.isArray (exchange.countries)) {
|
|
if (exchange.countries.indexOf (code) > -1)
|
|
shouldInclude = true
|
|
} else {
|
|
if (code == exchange.countries)
|
|
shouldInclude = true
|
|
}
|
|
if (shouldInclude) {
|
|
let entry = [
|
|
country,
|
|
'[](' + url + ')',
|
|
exchange.id,
|
|
'[' + exchange.name + '](' + url + ')',
|
|
version,
|
|
'[API](' + doc + ')',
|
|
// doesn't fit in width
|
|
// exchange.certified ? ccxtCertifiedBadge : '',
|
|
]
|
|
result.push (entry)
|
|
}
|
|
})
|
|
exchangesByCountries = exchangesByCountries.concat (result)
|
|
});
|
|
|
|
const countryKeyIndex = exchangesByCountryHeading.indexOf ('country / region')
|
|
exchangesByCountries = exchangesByCountries.sort ((a, b) => {
|
|
const countryA = a[countryKeyIndex].toLowerCase ()
|
|
const countryB = b[countryKeyIndex].toLowerCase ()
|
|
if (countryA > countryB) {
|
|
return 1
|
|
} else if (countryA < countryB) {
|
|
return -1;
|
|
} else {
|
|
if (a['id'] > b['id'])
|
|
return 1;
|
|
else if (a['id'] < b['id'])
|
|
return -1;
|
|
else
|
|
return 0;
|
|
}
|
|
})
|
|
|
|
exchangesByCountries.splice (0, 0, exchangesByCountryHeading)
|
|
const lines = makeTable (exchangesByCountries)
|
|
const result = "# Exchanges By Country\n\nThe ccxt library currently supports the following cryptocurrency exchange markets and trading APIs:\n\n" + lines + "\n\n"
|
|
for (const path of exchangesByCountriesPaths) {
|
|
fs.truncateSync (path)
|
|
fs.writeFileSync (path, result)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: REWRITE THIS ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
|
|
// ----------------------------------------------------------------------------
|
|
|
|
function exportExchangeIdsToExchangesJson (exchanges) {
|
|
log.bright ('Exporting exchange ids to'.cyan, 'exchanges.json'.yellow)
|
|
const ids = keys (exchanges)
|
|
console.log (ids)
|
|
fs.writeFileSync ('exchanges.json', JSON.stringify ({ ids }, null, 4))
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
function exportWikiToGitHub (wikiPath, gitWikiPath) {
|
|
|
|
log.bright.cyan ('Exporting wiki to GitHub')
|
|
|
|
const ccxtWikiFiles = {
|
|
'README.md': 'Home.md',
|
|
'Install.md': 'Install.md',
|
|
'Manual.md': 'Manual.md',
|
|
'Exchange-Markets.md': 'Exchange-Markets.md',
|
|
'Exchange-Markets-By-Country.md': 'Exchange-Markets-By-Country.md',
|
|
'ccxt.pro.md': 'ccxt.pro.md',
|
|
'ccxt.pro.install.md': 'ccxt.pro.install.md',
|
|
'ccxt.pro.manual.md': 'ccxt.pro.manual.md',
|
|
}
|
|
|
|
for (const [ sourceFile, destinationFile ] of entries (ccxtWikiFiles)) {
|
|
|
|
const sourcePath = wikiPath + '/' + sourceFile
|
|
const destinationPath = gitWikiPath + '/' + destinationFile
|
|
log.bright.cyan ('Exporting', sourcePath.yellow, '→', destinationPath.yellow)
|
|
fs.writeFileSync (destinationPath, fs.readFileSync (sourcePath))
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
function exportKeywordsToPackageJson (exchanges) {
|
|
|
|
log.bright ('Exporting exchange keywords to'.cyan, 'package.json'.yellow)
|
|
|
|
// const packageJSON = require ('../package.json')
|
|
const packageJSON = JSON.parse (fs.readFileSync ('./package.json'))
|
|
const keywords = new Set (packageJSON.keywords)
|
|
|
|
for (const ex of values (exchanges)) {
|
|
for (const url of Array.isArray (ex.urls.www) ? ex.urls.www : [ex.urls.www]) {
|
|
keywords.add (url.replace (/(http|https):\/\/(www\.)?/, '').replace (/\/.*/, ''))
|
|
}
|
|
keywords.add (ex.name)
|
|
}
|
|
|
|
packageJSON.keywords = [...keywords]
|
|
fs.writeFileSync ('./package.json', JSON.stringify (packageJSON, null, 2) + "\n")
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
function flatten (nested, result = []) {
|
|
for (const key in nested) {
|
|
result.push (key)
|
|
if (Object.keys (nested[key]).length)
|
|
flatten (nested[key], result)
|
|
}
|
|
return result
|
|
}
|
|
|
|
|
|
function exportEverything () {
|
|
const ids = getIncludedExchangeIds ()
|
|
const errorHierarchy = require ('../js/base/errorHierarchy.js')
|
|
const flat = flatten (errorHierarchy)
|
|
flat.push ('error_hierarchy')
|
|
|
|
const replacements = [
|
|
{
|
|
file: './ccxt.js',
|
|
regex: /(?:const|var)\s+exchanges\s+\=\s+\{[^\}]+\}/,
|
|
replacement: "const exchanges = {\n" + ids.map (id => (" '" + id + "':").padEnd (30) + " require ('./js/" + id + ".js'),").join ("\n") + " \n}",
|
|
},
|
|
{
|
|
file: './python/ccxt/__init__.py',
|
|
regex: /exchanges \= \[[^\]]+\]/,
|
|
replacement: "exchanges = [\n" + " '" + ids.join ("',\n '") + "'," + "\n]",
|
|
},
|
|
{
|
|
file: './python/ccxt/__init__.py',
|
|
regex: /(?:from ccxt\.[^\.]+ import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]exchanges/,
|
|
replacement: ids.map (id => ('from ccxt.' + id + ' import ' + id).padEnd (60) + '# noqa: F401').join ("\n") + "\n\nexchanges",
|
|
},
|
|
{
|
|
file: './python/ccxt/__init__.py',
|
|
regex: /(?:from ccxt\.base\.errors import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]/,
|
|
replacement: flat.map (error => ('from ccxt.base.errors' + ' import ' + error).padEnd (60) + '# noqa: F401').join ("\n") + "\n\n",
|
|
},
|
|
{
|
|
file: './python/ccxt/async_support/__init__.py',
|
|
regex: /(?:from ccxt\.base\.errors import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]/,
|
|
replacement: flat.map (error => ('from ccxt.base.errors' + ' import ' + error).padEnd (60) + '# noqa: F401').join ("\n") + "\n\n",
|
|
},
|
|
{
|
|
file: './python/ccxt/async_support/__init__.py',
|
|
regex: /(?:from ccxt\.async_support\.[^\.]+ import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]exchanges/,
|
|
replacement: ids.map (id => ('from ccxt.async_support.' + id + ' import ' + id).padEnd (74) + '# noqa: F401').join ("\n") + "\n\nexchanges",
|
|
},
|
|
{
|
|
file: './python/ccxt/async_support/__init__.py',
|
|
regex: /exchanges \= \[[^\]]+\]/,
|
|
replacement: "exchanges = [\n" + " '" + ids.join ("',\n '") + "'," + "\n]",
|
|
},
|
|
{
|
|
file: './php/base/Exchange.php',
|
|
regex: /public static \$exchanges \= array\s*\([^\)]+\)/,
|
|
replacement: "public static $exchanges = array(\n '" + ids.join ("',\n '") + "',\n )",
|
|
},
|
|
]
|
|
|
|
exportExchanges (replacements)
|
|
|
|
// strategically placed exactly here (we can require it AFTER the export)
|
|
const exchanges = createExchanges (ids)
|
|
|
|
const wikiPath = 'wiki'
|
|
, gitWikiPath = 'build/ccxt.wiki'
|
|
|
|
cloneGitHubWiki (gitWikiPath)
|
|
|
|
exportSupportedAndCertifiedExchanges (exchanges, {
|
|
allExchangesPaths: [
|
|
'README.md',
|
|
wikiPath + '/Manual.md',
|
|
wikiPath + '/Exchange-Markets.md'
|
|
],
|
|
certifiedExchangesPaths: [
|
|
'README.md',
|
|
],
|
|
exchangesByCountriesPaths: [
|
|
wikiPath + '/Exchange-Markets-By-Country.md'
|
|
],
|
|
proExchangesPaths: [
|
|
wikiPath + '/ccxt.pro.manual.md',
|
|
],
|
|
})
|
|
|
|
exportExchangeIdsToExchangesJson (exchanges)
|
|
exportWikiToGitHub (wikiPath, gitWikiPath)
|
|
exportKeywordsToPackageJson (exchanges)
|
|
|
|
log.bright.green ('Exported successfully.')
|
|
}
|
|
|
|
// ============================================================================
|
|
// main entry point
|
|
|
|
if (require.main === module) {
|
|
|
|
// if called directly like `node module`
|
|
|
|
exportEverything ()
|
|
|
|
} else {
|
|
|
|
// do nothing if required as a module
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
module.exports = {
|
|
cloneGitHubWiki,
|
|
getIncludedExchangeIds,
|
|
exportExchanges,
|
|
createExchanges,
|
|
exportSupportedAndCertifiedExchanges,
|
|
exportExchangeIdsToExchangesJson,
|
|
exportWikiToGitHub,
|
|
exportKeywordsToPackageJson,
|
|
exportEverything,
|
|
}
|