ccxt-compiled
Version:
A JavaScript / Python / PHP cryptocurrency trading library with support for 90+ exchanges
684 lines (549 loc) • 29.9 kB
JavaScript
const fs = require ('fs')
const path = require ('path')
const log = require ('ololog')
const ansi = require ('ansicolor').nice
// ----------------------------------------------------------------------------
const { capitalize } = require ('./js/base/functions.js')
// ----------------------------------------------------------------------------
const errors = require ('./js/base/errors.js')
// ---------------------------------------------------------------------------
function replaceInFile (filename, regex, replacement) {
let contents = fs.readFileSync (filename, 'utf8')
const parts = contents.split (regex)
const newContents = parts[0] + replacement + parts[1]
fs.truncateSync (filename)
fs.writeFileSync (filename, newContents)
}
// ----------------------------------------------------------------------------
function regexAll (text, array) {
for (let i in array) {
let regex = array[i][0]
regex = typeof regex == 'string' ? new RegExp (regex, 'g') : new RegExp (regex)
text = text.replace (regex, array[i][1])
}
return text
}
// ----------------------------------------------------------------------------
const commonRegexes = [
[ /\.deepExtend\s/g, '.deep_extend'],
[ /\.safeFloat\s/g, '.safe_float'],
[ /\.safeInteger\s/g, '.safe_integer'],
[ /\.safeString\s/g, '.safe_string'],
[ /\.safeValue\s/g, '.safe_value'],
[ /\.arrayConcat\s/g, '.array_concat'],
[ /\.binaryConcat\s/g, '.binary_concat'],
[ /\.binaryToString\s/g, '.binary_to_string' ],
[ /\.precisionFromString\s/g, '.precision_from_string'],
[ /\.implodeParams\s/g, '.implode_params'],
[ /\.extractParams\s/g, '.extract_params'],
[ /\.parseBalance\s/g, '.parse_balance'],
[ /\.parseOHLCVs\s/g, '.parse_ohlcvs'],
[ /\.parseOHLCV\s/g, '.parse_ohlcv'],
[ /\.parseTicker\s/g, '.parse_ticker'],
[ /\.parseTradesData\s/g, '.parse_trades_data'],
[ /\.parseTrades\s/g, '.parse_trades'],
[ /\.parseTrade\s/g, '.parse_trade'],
[ /\.parseOrderBook\s/g, '.parse_order_book'],
[ /\.parseBidsAsks\s/g, '.parse_bids_asks'],
[ /\.parseBidAsk\s/g, '.parse_bid_ask'],
[ /\.parseOrders\s/g, '.parse_orders'],
[ /\.parseOrderStatus\s/g, '.parse_order_status'],
[ /\.parseOrder\s/g, '.parse_order'],
[ /\.filterBySinceLimit\s/g, '.filter_by_since_limit'],
[ /\.filterOrdersBySymbol\s/g, '.filter_orders_by_symbol'],
[ /\.getVersionString\s/g, '.get_version_string'],
[ /\.indexBy\s/g, '.index_by'],
[ /\.sortBy\s/g, '.sort_by'],
[ /\.filterBy\s/g, '.filter_by'],
[ /\.groupBy\s/g, '.group_by'],
[ /\.marketIds\s/g, '.market_ids'],
[ /\.marketId\s/g, '.market_id'],
[ /\.fetchL2OrderBook\s/g, '.fetch_l2_order_book'],
[ /\.fetchOrderBook\s/g, '.fetch_order_book'],
[ /\.fetchMyTrades\s/g, '.fetch_my_trades'],
[ /\.fetchOrderStatus\s/g, '.fetch_order_status'],
[ /\.fetchOpenOrders\s/g, '.fetch_open_orders'],
[ /\.fetchOrders\s/g, '.fetch_orders'],
[ /\.fetchOrder\s/g, '.fetch_order'],
[ /\.fetchTickers\s/g, '.fetch_tickers'],
[ /\.fetchTicker\s/g, '.fetch_ticker'],
[ /\.fetchCurrencies\s/g, '.fetch_currencies'],
[ /\.priceToPrecision\s/g, '.price_to_precision'],
[ /\.amountToPrecision\s/g, '.amount_to_precision'],
[ /\.amountToLots\s/g, '.amount_to_lots'],
[ /\.feeToPrecision\s/g, '.fee_to_precision'],
[ /\.costToPrecision\s/g, '.cost_to_precision'],
[ /\.commonCurrencyCode\s/g, '.common_currency_code'],
[ /\.loadMarkets\s/g, '.load_markets'],
[ /\.fetchMarkets\s/g, '.fetch_markets'],
[ /\.appendInactiveMarkets\s/g, '.append_inactive_markets'],
[ /\.fetchCategories\s/g, '.fetch_categories'],
[ /\.calculateFee\s/g, '.calculate_fee'],
[ /\.editLimitBuyOrder\s/g, '.edit_limit_buy_order'],
[ /\.editLimitSellOrder\s/g, '.edit_limit_sell_order'],
[ /\.editLimitOrder\s/g, '.edit_limit_order'],
[ /\.editOrder\s/g, '.edit_order'],
[ /\.encodeURIComponent\s/g, '.encode_uri_component'],
[ /\.handleErrors\s/g, '.handle_errors'],
[ /\.checkRequiredCredentials\s/g, '.check_required_credentials'],
]
// ----------------------------------------------------------------------------
const pythonRegexes = [
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\s+\'undefined\'/g, '$1[$2] is None' ],
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\s+\'undefined\'/g, '$1[$2] is not None' ],
[ /typeof\s+([^\s]+)\s+\=\=\s+\'undefined\'/g, '$1 is None' ],
[ /typeof\s+([^\s]+)\s+\!\=\s+\'undefined\'/g, '$1 is not None' ],
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\s+\'string\'/g, 'isinstance($1[$2], basestring)' ],
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\s+\'string\'/g, 'not isinstance($1[$2], basestring)' ],
[ /typeof\s+([^\s]+)\s+\=\=\s+\'string\'/g, 'isinstance($1, basestring)' ],
[ /typeof\s+([^\s]+)\s+\!\=\s+\'string\'/g, 'not isinstance($1, basestring)' ],
[ /undefined/g, 'None' ],
[ /this\.stringToBinary\s*\((.*)\)/g, '$1' ],
[ /this\.stringToBase64\s/g, 'base64.b64encode' ],
[ /this\.base64ToBinary\s/g, 'base64.b64decode' ],
// insert common regexes in the middle (critical)
].concat (commonRegexes).concat ([
// [ /this\.urlencode\s/g, '_urlencode.urlencode ' ], // use self.urlencode instead
[ /this\./g, 'self.' ],
[ /([^a-zA-Z\'])this([^a-zA-Z])/g, '$1self$2' ],
[ /([^a-zA-Z0-9_])let\s\[\s*([^\]]+)\s\]/g, '$1$2' ],
[ /([^a-zA-Z0-9_])let\s\{\s*([^\}]+)\s\}\s\=\s([^\;]+)/g, '$1$2 = (lambda $2: ($2))(**$3)' ],
[ /([^a-zA-Z0-9_])let\s/g, '$1' ],
[ /Object\.keys\s*\((.*)\)\.length/g, '$1' ],
[ /Object\.keys\s*\((.*)\)/g, 'list($1.keys())' ],
[ /\[([^\]]+)\]\.join\s*\(([^\)]+)\)/g, "$2.join([$1])" ],
[ /hash \(([^,]+)\, \'(sha[0-9])\'/g, "hash($1, '$2'" ],
[ /hmac \(([^,]+)\, ([^,]+)\, \'(md5)\'/g, 'hmac($1, $2, hashlib.$3' ],
[ /hmac \(([^,]+)\, ([^,]+)\, \'(sha[0-9]+)\'/g, 'hmac($1, $2, hashlib.$3' ],
[ /throw new ([\S]+) \((.*)\)/g, 'raise $1($2)'],
[ /throw ([\S]+)/g, 'raise $1'],
[ /try {/g, 'try:'],
[ /\}\s+catch \(([\S]+)\) {/g, 'except Exception as $1:'],
[ /([\s\(])extend(\s)/g, '$1self.extend$2' ],
[ /\} else if/g, 'elif' ],
[ /if\s+\((.*)\)\s+\{/g, 'if $1:' ],
[ /if\s+\((.*)\)\s*[\n]/g, "if $1:\n" ],
[ /\}\s*else\s*\{/g, 'else:' ],
[ /else\s*[\n]/g, "else:\n" ],
[ /for\s+\(([a-zA-Z0-9_]+)\s*=\s*([^\;\s]+\s*)\;[^\<\>\=]+(?:\<=|\>=|<|>)\s*(.*)\.length\s*\;[^\)]+\)\s*{/g, 'for $1 in range($2, len($3)):'],
[ /\s\|\|\s/g, ' or ' ],
[ /\s\&\&\s/g, ' and ' ],
[ /\!([^\=])/g, 'not $1'],
[ /([^\s]+)\.length/g, 'len($1)' ],
[ /\.push\s*\(([\s\S]+?)\);/g, '.append($1);' ],
[ /^\s*}\s*$/gm, '' ],
[ /;/g, '' ],
[ /\.toUpperCase\s*/g, '.upper' ],
[ /\.toLowerCase\s*/g, '.lower' ],
[ /JSON\.stringify\s*/g, 'json.dumps' ],
[ /JSON\.parse\s*/g, "json.loads" ],
// [ /([^\(\s]+)\.includes\s+\(([^\)]+)\)/g, '$2 in $1' ],
// [ /\'%([^\']+)\'\.sprintf\s*\(([^\)]+)\)/g, "'{:$1}'.format($2)" ],
[ /([^\s]+)\.toFixed\s*\(([0-9]+)\)/g, "'{:.$2f}'.format($1)" ],
[ /([^\s]+)\.toFixed\s*\(([^\)]+)\)/g, "('{:.' + str($2) + 'f}').format($1)" ],
[ /parseFloat\s*/g, 'float'],
[ /parseInt\s*/g, 'int'],
[ /self\[([^\]+]+)\]/g, 'getattr(self, $1)' ],
[ /([^\s]+)\.slice \(([^\,\)]+)\,\s?([^\)]+)\)/g, '$1[$2:$3]' ],
[ /([^\s]+)\.slice \(([^\)\:]+)\)/g, '$1[$2:]' ],
[ /Math\.floor\s*\(([^\)]+)\)/g, 'int(math.floor($1))' ],
[ /Math\.abs\s*\(([^\)]+)\)/g, 'abs($1)' ],
[ /Math\.pow\s*\(([^\)]+)\)/g, 'math.pow($1)' ],
[ /Math\.round\s*\(([^\)]+)\)/g, 'int(round($1))' ],
[ /Math\.ceil\s*\(([^\)]+)\)/g, 'int(ceil($1))' ],
[ /Math\.log/g, 'math.log' ],
[ /(\([^\)]+\)|[^\s]+)\s*\?\s*(\([^\)]+\)|[^\s]+)\s*\:\s*(\([^\)]+\)|[^\s]+)/g, '$2 if $1 else $3'],
[ / \/\//g, ' #' ],
[ /([^\n\s]) #/g, '$1 #' ], // PEP8 E261
[ /\.indexOf/g, '.find'],
[ /\strue/g, ' True'],
[ /\sfalse/g, ' False'],
[ /\(([^\s]+)\sin\s([^\)]+)\)/g, '($1 in list($2.keys()))' ],
[ /([^\s]+\s*\(\))\.toString\s+\(\)/g, 'str($1)' ],
[ /([^\s]+)\.toString \(\)/g, 'str($1)' ],
[ /([^\s]+)\.join\s*\(\s*([^\)\[\]]+?)\s*\)/g, '$2.join($1)' ],
[ /Math\.(max|min)\s/g, '$1' ],
[ /console\.log\s/g, 'print'],
[ /process\.exit\s+/g, 'sys.exit'],
[ /([^:+=\s]+) \(/g, '$1(' ], // PEP8 E225 remove whitespaces before left ( round bracket
[ /\[ /g, '[' ], // PEP8 E201 remove whitespaces after left [ square bracket
[ /\{ /g, '{' ], // PEP8 E201 remove whitespaces after left { bracket
[ /([^\s]+) \]/g, '$1]' ], // PEP8 E202 remove whitespaces before right ] square bracket
[ /([^\s]+) \}/g, '$1}' ], // PEP8 E202 remove whitespaces before right } bracket
[ /([^a-z])(elif|if|or)\(/g, '$1$2 \(' ], // a correction for PEP8 E225 side-effect for compound and ternary conditionals
[ /\=\=\sTrue/g, 'is True' ], // a correction for PEP8 E712, it likes "is True", not "== True"
])
// ----------------------------------------------------------------------------
const python2Regexes = [
[ /(\s)await(\s)/g, '$1' ]
]
// ----------------------------------------------------------------------------
const phpRegexes = [
[ /\{([a-zA-Z0-9_]+?)\}/g, '<$1>' ], // resolve the "arrays vs url params" conflict (both are in {}-brackets)
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\s+\'undefined\'/g, '$1[$2] == null' ],
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\s+\'undefined\'/g, '$1[$2] != null' ],
[ /typeof\s+([^\s]+)\s+\=\=\s+\'undefined\'/g, '$1 === null' ],
[ /typeof\s+([^\s]+)\s+\!\=\s+\'undefined\'/g, '$1 !== null' ],
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\s+\'string\'/g, "gettype ($1[$2]) == 'string'" ],
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\s+\'string\'/g, "gettype ($1[$2]) != 'string'" ],
[ /typeof\s+([^\s]+)\s+\=\=\s+\'string\'/g, "gettype ($1) == 'string'" ],
[ /typeof\s+([^\s]+)\s+\!\=\s+\'string\'/g, "gettype ($1) != 'string'" ],
[ /undefined/g, 'null' ],
[ /this\.extend/g, 'array_merge' ],
[ /this\.stringToBinary\s*\((.*)\)/g, '$1' ],
[ /this\.stringToBase64/g, 'base64_encode' ],
[ /this\.base64ToBinary/g, 'base64_decode' ],
[ /this\.deepExtend/g, 'array_replace_recursive'],
// insert common regexes in the middle (critical)
].concat (commonRegexes).concat ([
[ /this\./g, '$this->' ],
[ / this;/g, ' $this;' ],
[ /([^'])this_\./g, '$1$this_->' ],
[ /\{\}/g, 'array ()' ],
[ /\[\]/g, 'array ()' ],
[ /\{([^\n\}]+)\}/g, 'array ($1)' ],
[ /([^a-zA-Z0-9_])let\s\[\s*([^\]]+)\s\]/g, '$1list ($2)' ],
[ /([^a-zA-Z0-9_])let\s\{\s*([^\}]+)\s\}/g, '$1array_values (list ($2))' ],
[ /([^a-zA-Z0-9_])let\s/g, '$1' ],
[ /Object\.keys\s*\((.*)\)\.length/g, '$1' ],
[ /Object\.keys\s*\((.*)\)/g, 'is_array ($1) ? array_keys ($1) : array ()' ],
[ /([^\s]+\s*\(\))\.toString \(\)/g, '(string) $1' ],
[ /([^\s]+)\.toString \(\)/g, '(string) $1' ],
[ /throw new Error \((.*)\)/g, 'throw new \\Exception ($1)'],
[ /throw new ([\S]+) \((.*)\)/g, 'throw new $1 ($2)'],
[ /throw ([\S]+)\;/g, 'throw $$$1;'],
[ /\}\s+catch \(([\S]+)\) {/g, '} catch (Exception $$$1) {'],
[ /for\s+\(([a-zA-Z0-9_]+)\s*=\s*([^\;\s]+\s*)\;[^\<\>\=]+(\<=|\>=|<|>)\s*(.*)\.length\s*\;([^\)]+)\)\s*{/g, 'for ($1 = $2; $1 $3 count ($4);$5) {'],
[ /([^\s]+)\.length\;/g, 'is_array ($1) ? count ($1) : 0;' ],
[ /([^\s\(]+)\.length/g, 'strlen ($1)' ],
[ /\.push\s*\(([\s\S]+?)\)\;/g, '[] = $1;' ],
[ /(\s)await(\s)/g, '$1' ],
[ /([\S])\: /g, '$1 => ' ],
// add {}-array syntax conversions up to 20 levels deep
]).concat ([ ... Array (20) ].map (x => [ /\{([^\;\{]+?)\}([^\s])/g, 'array ($1)$2' ])).concat ([
[ /\[\s*([^\]]+?)\s*\]\.join\s*\(\s*([^\)]+?)\s*\)/g, "implode ($2, array ($1))" ],
// add []-array syntax conversions up to 20 levels deep
]).concat ([ ... Array (20) ].map (x => [ /\[(\s[^\]]+?\s)\]/g, 'array ($1)' ])).concat ([
[ /JSON\.stringify/g, 'json_encode' ],
[ /JSON\.parse\s+\(([^\)]+)\)/g, 'json_decode ($1, $$as_associative_array = true)' ],
[ /([^\(\s]+)\.includes\s+\(([^\)]+)\)/g, 'mb_strpos ($1, $2)' ],
// [ /\'([^\']+)\'\.sprintf\s*\(([^\)]+)\)/g, "sprintf ('$1', $2)" ],
[ /([^\s]+)\.toFixed\s*\(([0-9]+)\)/g, "sprintf ('%.$2f', $1)" ],
[ /([^\s]+)\.toFixed\s*\(([^\)]+)\)/g, "sprintf ('%.' . $2 . 'f', $1)" ],
[ /parseFloat\s/g, 'floatval '],
[ /parseInt\s/g, 'intval '],
[ / \+ /g, ' . ' ],
[ / \+\= /g, ' .= ' ],
[ /([^\s]+(?:\s*\(.+\))?)\.toUpperCase\s*\(\)/g, 'strtoupper ($1)' ],
[ /([^\s]+(?:\s*\(.+\))?)\.toLowerCase\s*\(\)/g, 'strtolower ($1)' ],
[ /([^\s]+(?:\s*\(.+\))?)\.replace\s*\(([^\)]+)\)/g, 'str_replace ($2, $1)' ],
[ /this\[([^\]+]+)\]/g, '$$this->$$$1' ],
[ /([^\s]+).slice \(([^\)\:]+)\)/g, 'mb_substr ($1, $2)' ],
[ /([^\s]+).slice \(([^\,\)]+)\,\s*([^\)]+)\)/g, 'mb_substr ($1, $2, $3)' ],
[ /([^\s]+).split \(([^\,]+?)\)/g, 'explode ($2, $1)' ],
[ /Math\.floor\s*\(([^\)]+)\)/g, '(int) floor ($1)' ],
[ /Math\.abs\s*\(([^\)]+)\)/g, 'abs ($1)' ],
[ /Math\.round\s*\(([^\)]+)\)/g, '(int) round ($1)' ],
[ /Math\.ceil\s*\(([^\)]+)\)/g, '(int) ceil ($1)' ],
[ /Math\.pow\s*\(([^\)]+)\)/g, 'pow ($1)' ],
[ /Math\.log/g, 'log' ],
[ /([^\(\s]+)\s+%\s+([^\s\)]+)/g, 'fmod ($1, $2)' ],
[ /\(([^\s]+)\.indexOf\s*\(([^\)]+)\)\s*\>\=\s*0\)/g, '(mb_strpos ($1, $2) !== false)' ],
[ /([^\s]+)\.indexOf\s*\(([^\)]+)\)\s*\>\=\s*0/g, 'mb_strpos ($1, $2) !== false' ],
[ /([^\s]+)\.indexOf\s*\(([^\)]+)\)/g, 'mb_strpos ($1, $2)' ],
[ /\(([^\s\(]+)\sin\s([^\)]+)\)/g, '(is_array ($2) && array_key_exists ($1, $2))' ],
[ /([^\s]+)\.join\s*\(\s*([^\)]+?)\s*\)/g, 'implode ($2, $1)' ],
[ /Math\.(max|min)/g, '$1' ],
[ /console\.log/g, 'var_dump'],
[ /process\.exit/g, 'exit'],
[ /super\./g, 'parent::'],
[ /\<([a-zA-Z0-9_]+?)\>/g, '{$1}' ], // resolve the "arrays vs url params" conflict (both are in {}-brackets)
])
// ----------------------------------------------------------------------------
// one-time helpers
function createPythonClass (className, baseClass, body, methods, async = false) {
const pythonStandardLibraries = {
'base64': 'base64',
'hashlib': 'hashlib',
'math': 'math',
'json.loads': 'json',
}
async = async ? 'async.' : ''
const importFrom = (baseClass == 'Exchange') ?
('ccxt.' + async + 'base.exchange') :
('ccxt.' + async + baseClass)
let bodyAsString = body.join ("\n")
const header = [
"# -*- coding: utf-8 -*-\n",
'from ' + importFrom + ' import ' + baseClass,
... (bodyAsString.match (/basestring/) ? [
"\n# -----------------------------------------------------------------------------\n",
"try:",
" basestring # Python 3",
"except NameError:",
" basestring = str # Python 2\n\n",
] : [])
]
for (let library in pythonStandardLibraries) {
const regex = new RegExp ("[^\\']" + library + "[^\\'a-zA-Z]")
if (bodyAsString.match (regex))
header.push ('import ' + pythonStandardLibraries[library])
}
for (let error in errors) {
const regex = new RegExp ("[^\\']" + error + "[^\\']")
if (bodyAsString.match (regex))
header.push ('from ccxt.base.errors import ' + error)
}
for (let method of methods) {
const regex = new RegExp ('self\\.(' + method + ')\\s*\\(', 'g')
bodyAsString = bodyAsString.replace (regex,
(match, p1) => ('self.' + convertMethodNameToUnderscoreNotation (p1) + '('))
}
header.push ("\n\nclass " + className + ' (' + baseClass + '):')
const footer = [
'', // footer (last empty line)
]
const result = header.join ("\n") + "\n" + bodyAsString + "\n" + footer.join ('\n')
return result
}
// ----------------------------------------------------------------------------
function createPHPClass (className, baseClass, body, methods) {
const baseFolder = (baseClass == 'Exchange') ? 'base/' : ''
const baseFile = baseFolder + baseClass + '.php'
const header = [
"<?php\n",
"namespace ccxt;\n",
'class ' + className + ' extends ' + baseClass + ' {' ,
]
let bodyAsString = body.join ("\n")
for (let method of methods) {
const regex = new RegExp ('this->(' + method + ')\\s*\\(', 'g')
bodyAsString = bodyAsString.replace (regex,
(match, p1) => ('this->' + convertMethodNameToUnderscoreNotation (p1) + ' ('))
}
const footer =[
"}\n",
]
const result = header.join ("\n") + "\n" + bodyAsString + "\n" + footer.join ('\n')
return result
}
// ----------------------------------------------------------------------------
const python2Folder = './python/ccxt/'
const python3Folder = './python/ccxt/async/'
const phpFolder = './php/'
// ----------------------------------------------------------------------------
function convertMethodNameToUnderscoreNotation (method) {
return (method
.replace (/[A-Z]+/g, match => capitalize (match.toLowerCase ()))
.replace (/[A-Z]/g, match => '_' + match.toLowerCase ()))
}
// ----------------------------------------------------------------------------
function transpileDerivedExchangeClass (contents) {
// match all required imports
let requireRegex = /^const\s+[^\=]+\=\s*require\s*\(\'[^\']+\'\)$/gm
let requireMatches = contents.match (requireRegex)
// log.yellow (requireMatches)
// process.exit (1)
let exchangeClassDeclarationMatches = contents.match (/^module\.exports\s*=\s*class\s+([\S]+)\s+extends\s+([\S]+)\s+{([\s\S]+?)^}/m)
// log.green (file, exchangeClassDeclarationMatches[3])
let className = exchangeClassDeclarationMatches[1]
let baseClass = exchangeClassDeclarationMatches[2]
let methods = exchangeClassDeclarationMatches[3].trim ().split (/\n\s*\n/)
let python2 = []
let python3 = []
let php = []
let methodNames = []
// run through all methods
for (let i = 0; i < methods.length; i++) {
// parse the method signature
let part = methods[i].trim ()
let lines = part.split ("\n")
let signature = lines[0].trim ()
let methodSignatureRegex = /(async |)([\S]+)\s\(([^)]*)\)\s*{/ // signature line
let matches = methodSignatureRegex.exec (signature)
let keyword = ''
try {
// async or not
keyword = matches[1]
} catch (e) {
log.red (e)
log.green (methods[i])
log.yellow (exchangeClassDeclarationMatches[3].trim ().split (/\n\s*\n/))
process.exit ()
}
// method name
let method = matches[2]
methodNames.push (method)
method = convertMethodNameToUnderscoreNotation (method)
// method arguments
let args = matches[3].trim ()
// extract argument names and local variables
args = args.length ? args.split (',').map (x => x.trim ()) : []
// get names of all method arguments for later substitutions
let variables = args.map (arg => arg.split ('=').map (x => x.trim ()) [0])
// add $ to each argument name in PHP method signature
let phpArgs = args.join (', $').trim ().replace (/undefined/g, 'null').replace ('{}', 'array ()')
phpArgs = phpArgs.length ? ('$' + phpArgs) : ''
// remove excessive spacing from argument defaults in Python method signature
let pythonArgs = args.map (x => x.replace (' = ', '='))
.join (', ')
.replace (/undefined/g, 'None')
.replace (/false/g, 'False')
.replace (/true/g, 'True')
// method body without the signature (first line)
// and without the closing bracket (last line)
let body = lines.slice (1, -1).join ("\n")
// match all local variables (let, const or var)
let localVariablesRegex = /[^a-zA-Z0-9_](?:let|const|var)\s+(?:\[([^\]]+)\]|([a-zA-Z0-9_]+))/g // local variables
// process the variables created in destructuring assignments as well
let localVariablesMatches
while (localVariablesMatches = localVariablesRegex.exec (body)) {
let match = localVariablesMatches[1] ? localVariablesMatches[1] : localVariablesMatches[2]
match = match.trim ().split (', ') // split the destructuring assignment by comma
match.forEach (x => variables.push (x.trim ())) // trim each variable name
variables.push (localVariablesMatches[1]) // add them to the list of local variables
}
// append $ to all variables in the method (PHP syntax demands $ at the beginning of a variable name)
let phpVariablesRegexes = variables.map (x => [ "([^$$a-zA-Z0-9\\.\\>'_])" + x + "([^a-zA-Z0-9'_])", '$1$$' + x + '$2' ])
// transpile JS → Python 3
let python3Body = regexAll (body, pythonRegexes)
.replace (/$\s*$/gm, '')
.replace (/\'([абвгдеёжзийклмнопрстуфхцчшщъыьэюя]+)\'/gm, "u'$1'")
// special case for Python OrderedDicts
let orderedDictRegex = /\.ordered\s+\(\{([^\}]+)\}\)/g
let orderedDictMatches = undefined
while (orderedDictMatches = orderedDictRegex.exec (python3Body)) {
let replaced = orderedDictMatches[1].replace (/^(\s+)([^\:]+)\:\s*([^\,]+)\,$/gm, '$1($2, $3),')
python3Body = python3Body.replace (orderedDictRegex, '\.ordered ([' + replaced + '])')
}
// special case for Python super
python3Body = python3Body.replace (/super\./g, 'super(' + className + ', self).')
// remove await from Python 2 body
let python2Body = regexAll (python3Body, python2Regexes)
// compile the final Python code for the method signature
let pythonString = 'def ' + method + '(self' + (pythonArgs.length ? ', ' + pythonArgs : '') + '):'
// compile signature + body for Python 2
python2.push ('');
python2.push (' ' + pythonString);
python2.push (python2Body);
// compile signature + body for Python 3
python3.push ('');
python3.push (' ' + keyword + pythonString);
python3.push (python3Body);
// transpile JS → PHP
let phpBody = regexAll (body, phpRegexes.concat (phpVariablesRegexes))
// compile signature + body for PHP
php.push ('');
php.push (' public function ' + method + ' (' + phpArgs + ') {');
php.push (phpBody);
php.push (' }')
}
return {
// alltogether in PHP, Python 2 and 3
python2: createPythonClass (className, baseClass, python2, methodNames),
python3: createPythonClass (className, baseClass, python3, methodNames, true),
php: createPHPClass (className, baseClass, php, methodNames),
className,
baseClass,
}
}
// ----------------------------------------------------------------------------
function transpileDerivedExchangeFile (folder, filename) {
let contents = fs.readFileSync (folder + filename, 'utf8')
let { python2, python3, php, className, baseClass } = transpileDerivedExchangeClass (contents)
const python2Filename = python2Folder + filename.replace ('.js', '.py')
const python3Filename = python3Folder + filename.replace ('.js', '.py')
const phpFilename = phpFolder + filename.replace ('.js', '.php')
log.cyan ('Transpiling from', filename.yellow)
overwriteFile (python2Filename, python2)
overwriteFile (python3Filename, python3)
overwriteFile (phpFilename, php)
return { className, baseClass }
}
//-----------------------------------------------------------------------------
function transpileDerivedExchangeFiles (folder) {
const classNames = fs.readdirSync (folder)
.filter (file => file.includes ('.js'))
.map (file => transpileDerivedExchangeFile (folder, file))
let classes = {}
classNames.forEach (({ className, baseClass }) => {
classes[className] = baseClass
})
return classes
}
//-----------------------------------------------------------------------------
function copyFile (oldName, newName) {
let contents = fs.readFileSync (oldName, 'utf8')
fs.truncateSync (newName)
fs.writeFileSync (newName, contents)
}
//-----------------------------------------------------------------------------
function overwriteFile (filename, contents) {
// log.cyan ('Overwriting → ' + filename.yellow)
fs.closeSync (fs.openSync (filename, 'a'));
fs.truncateSync (filename)
fs.writeFileSync (filename, contents)
}
//----------------------------------------------------------------------------
function createFolder (folder) {
try {
fs.mkdirSync (folder)
} catch (err) {
if (err.code !== 'EEXIST') {
throw err
}
}
}
//-----------------------------------------------------------------------------
function createFolderRecursively (folder) {
const parts = folder.split (path.sep)
for (let i = 1; i <= parts.length; i++) {
createFolder (path.join.apply (null, parts.slice (0, i)))
}
}
//-----------------------------------------------------------------------------
function transpilePythonAsyncToSync (oldName, newName) {
log.magenta ('Transpiling ' + oldName.yellow + ' → ' + newName.yellow)
const fileContents = fs.readFileSync (oldName, 'utf8')
let lines = fileContents.split ("\n")
lines = lines.filter (line => ![ 'import asyncio' ].includes (line))
.map (line => {
return (
line.replace ('asyncio.get_event_loop().run_until_complete(main())', 'main()')
.replace ('import ccxt.async as ccxt', 'import ccxt')
.replace (/.*token\_bucket.*/g, '')
.replace ('async ', '')
.replace ('await ', ''))
})
// lines.forEach (line => log (line))
function deleteFunction (f, from) {
const re1 = new RegExp ('def ' + f + '[^\#]+', 'g')
const re2 = new RegExp ('[\\s]+' + f + '\\(exchange\\)', 'g')
return from.replace (re1, '').replace (re2, '')
}
let newContents = lines.join ('\n')
newContents = deleteFunction ('test_tickers_async', newContents)
newContents = deleteFunction ('test_l2_order_books_async', newContents)
fs.truncateSync (newName)
fs.writeFileSync (newName, newContents)
}
//-----------------------------------------------------------------------------
function exportTypeScriptDeclarations (classes) {
const file = './ccxt.d.ts'
const regex = /(?: export class [^\s]+ extends [^\s]+ \{\}[\r]?[\n])+/
const replacement = Object.keys (classes).map (className => {
const baseClass = classes[className]
return ' export class ' + className + ' extends ' + baseClass + " {}"
}).join ("\n") + "\n"
replaceInFile (file, regex, replacement)
}
//-----------------------------------------------------------------------------
createFolderRecursively (python2Folder)
createFolderRecursively (python3Folder)
createFolderRecursively (phpFolder)
const classes = transpileDerivedExchangeFiles ('./js/')
exportTypeScriptDeclarations (classes)
transpilePythonAsyncToSync ('./python/test/test_async.py', './python/test/test.py')
//-----------------------------------------------------------------------------
log.bright.green ('Transpiled successfully.')
;