aspernaturet
Version:
A library for cryptocurrency trading and e-commerce with support for many bitcoin/ether/altcoin exchange markets and merchant APIs
380 lines (330 loc) • 18.2 kB
JavaScript
const fs = require ('fs')
const log = require ('ololog')
const ansi = require ('ansicolor').nice
//-----------------------------------------------------------------------------
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
}
//-----------------------------------------------------------------------------
let ccxtjs = fs.readFileSync ('ccxt.js', 'utf8')
let contents = ccxtjs.match (/\/\/====(?:[\s\S]+?)\/\/====/) [0]
let exchanges
let regex = /^var ([\S]+) =\s*(?:extend\s*\(([^\,]+)\,\s*)?{([\s\S]+?)^}/gm // exchange class
let python = []
let pythonAsync = []
let php = []
//-----------------------------------------------------------------------------
while (exchanges = regex.exec (contents)) {
let id = exchanges[1]
let parent = exchanges[2]
let all = exchanges[3].trim ().split (/\,\s*\n\s*\n/)
let params = ' ' + all[0]
let methods = all.slice (1)
let py = []
let pyAsync = []
let ph = []
params = params.split ("\n")
let pyParams = params
.join ("\n ")
.replace (/ true/g, ' True')
.replace (/ false/g, ' False')
.replace (/ \/\//g, ' #')
.replace (/\{ /g, '{') // PEP8 E201
.replace (/\[ /g, '[') // PEP8 E201
.replace (/([^\s]+) \]/g, '$1]') // PEP8 E202
.replace (/([^\s]+) \}\,/g, '$1},') // PEP8 E202
function pyAddClass (py) {
py.push ('')
py.push ('class ' + id + ' (' + (parent ? parent : 'Exchange') + '):')
py.push ('')
py.push (' def __init__(self, config={}):')
py.push (' params = {')
py.push (' ' + pyParams + ((all.length > 1) ? ',' : ''))
py.push (' }')
py.push (' params.update(config)')
py.push (' super(' + id + ', self).__init__(params)')
}
pyAddClass (py);
pyAddClass (pyAsync);
ph.push ('')
ph.push ('class ' + id + ' extends ' + (parent ? parent : 'Exchange') + ' {')
ph.push ('')
ph.push (' public function __construct ($options = array ()) {')
ph.push (' parent::__construct (array_merge(array (')
ph.push (' ' + params.join ("\n ").replace (/': /g, "' => ").replace (/ {/g, ' array (').replace (/ \[/g, ' array (').replace (/\}([\,\n]|$)/g, ')$1').replace (/\]/g, ')') + ((all.length > 1) ? ',' : ''))
ph.push (' ), $options));')
ph.push (' }')
for (let i = 0; i < methods.length; i++) {
let part = methods[i].trim ()
let lines = part.split ("\n")
let header = lines[0].trim ()
let regex2 = /(async |)([\S]+)\s\(([^)]*)\)\s*{/g // exchange method
let matches = regex2.exec (header)
let keyword = matches[1]
let method = matches[2]
let args = matches[3].trim ()
method = method.replace ('fetchBalance', 'fetch_balance')
// .replace ('fetchCategories', 'fetch_categories')
.replace ('loadMarkets', 'load_markets')
.replace ('fetchMarkets', 'fetch_markets')
.replace ('fetchOrderBook', 'fetch_order_book')
.replace ('fetchOHLCV', 'fetch_ohlcv')
.replace ('parseOHLCVs', 'parse_ohlcvs')
.replace ('parseOHLCV', 'parse_ohlcv')
.replace ('fetchTickers', 'fetch_tickers')
.replace ('fetchTicker', 'fetch_ticker')
.replace ('parseTicker', 'parse_ticker')
.replace ('parseTrades', 'parse_trades')
.replace ('parseTrade', 'parse_trade')
.replace ('parseBidAsks', 'parse_bidasks')
.replace ('parseBidAsk', 'parse_bidask')
.replace ('parseOrders', 'parse_orders')
.replace ('parseOrder', 'parse_order')
.replace ('fetchTrades', 'fetch_trades')
.replace ('fetchOrderStatus', 'fetch_order_status')
.replace ('fetchOrderTrades', 'fetch_order_trades')
.replace ('fetchOrder', 'fetch_order')
.replace ('fetchOpenOrders', 'fetch_open_orders')
.replace ('fetchMyTrades', 'fetch_my_trades')
.replace ('fetchAllMyTrades', 'fetch_all_my_trades')
.replace ('createOrder', 'create_order')
.replace ('cancelOrder', 'cancel_order')
.replace ('signIn', 'sign_in')
args = args.length ? args.split (',').map (x => x.trim ()) : []
let phArgs = args.join (', $').trim ()
phArgs = phArgs.length ? ('$' + phArgs) : ''
let pyArgs = args.map (x => x.replace (' = ', '=')).join (', ')
let variables = args.map (arg => arg.split ('=').map (x => x.trim ()) [0])
let body = lines.slice (1, -1).join ("\n")
let regex3 = /[^a-zA-Z0-9_]let\s+(?:\[([^\]]+)\]|([a-zA-Z0-9_]+))/g // local variables
let localVariablesMatches
while (localVariablesMatches = regex3.exec (body)) {
let m = localVariablesMatches[1] ? localVariablesMatches[1] : localVariablesMatches[2]
m = m.trim ().split (', ')
m.forEach (x => variables.push (x.trim ()))
variables.push (localVariablesMatches[1])
}
let phVarsRegex = variables.map (x => [ "([^$$a-zA-Z0-9\\.\\>'_])" + x + "([^a-zA-Z0-9'_])", '$1$$' + x + '$2' ])
let pyRegex = [
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\s+\'undefined\'/g, '$1[$2] is None' ],
[ /undefined/g, 'None' ],
[ /this\.stringToBinary\s*\((.*)\)/g, '$1' ],
[ /this\.stringToBase64\s/g, 'base64.b64encode' ],
[ /this\.base64ToBinary\s/g, 'base64.b64decode' ],
[ /\.binaryConcat\s/g, '.binary_concat'],
[ /\.binaryToString\s/g, '.binary_to_string' ],
[ /\.implodeParams\s/g, '.implode_params'],
[ /\.extractParams\s/g, '.extract_params'],
[ /\.parseOHLCVs/g, '.parse_ohlcvs'],
[ /\.parseOHLCV/g, '.parse_ohlcv'],
[ /\.parseTicker\s/g, '.parse_ticker'],
[ /\.parseTrades\s/g, '.parse_trades'],
[ /\.parseTrade\s/g, '.parse_trade'],
[ /\.parseBidAsks\s/g, '.parse_bidasks'],
[ /\.parseBidAsk\s/g, '.parse_bidask'],
[ /\.indexBy\s/g, '.index_by'],
[ /\.sortBy\s/g, '.sort_by'],
[ /\.marketIds\s/g, '.market_ids'],
[ /\.marketId\s/g, '.market_id'],
[ /\.fetchOrderStatus\s/g, '.fetch_order_status'],
[ /\.fetchOpenOrders\s/g, '.fetch_open_orders'],
[ /\.parseOrders\s/g, '.parse_orders'],
[ /\.parseOrder\s/g, '.parse_order'],
[ /\.loadMarkets\s/g, '.load_markets'],
[ /\.encodeURIComponent\s/g, '.encode_uri_component'],
// [ /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/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*[\n]/gm, '' ],
[ /;/g, '' ],
[ /\.toUpperCase\s*/g, '.upper' ],
[ /\.toLowerCase\s*/g, '.lower' ],
[ /JSON\.stringify\s*/g, 'json.dumps' ],
[ /\'%([^\']+)\'\.sprintf\s*\(([^\)]+)\)/g, "'{:$1}'.format($2)" ],
[ /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\.round\s*\(([^\)]+)\)/g, 'int(round($1))' ],
[ /(\([^\)]+\)|[^\s]+)\s*\?\s*(\([^\)]+\)|[^\s]+)\s*\:\s*(\([^\)]+\)|[^\s]+)/g, '$2 if $1 else $3'],
[/ \/\//g, ' #' ],
[ /\.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
]
let phRegex = [
[ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\s+\'undefined\'/g, '$1[$2] == null' ],
[ /undefined/g, 'null' ],
[ /this\.extend/g, 'array_merge' ],
[ /this\.stringToBinary\s*\((.*)\)/g, '$1' ],
[ /this\.stringToBase64/g, 'base64_encode' ],
[ /this\.base64ToBinary/g, 'base64_decode' ],
[ /\.parseOHLCVs/g, '.parse_ohlcvs'],
[ /\.parseOHLCV/g, '.parse_ohlcv'],
[ /\.parseTicker/g, '.parse_ticker'],
[ /\.parseTrades/g, '.parse_trades'],
[ /\.parseTrade/g, '.parse_trade'],
[ /\.parseBidAsks/g, '.parse_bidasks'],
[ /\.parseBidAsk/g, '.parse_bidask'],
[ /\.binaryConcat/g, '.binary_concat'],
[ /\.binaryToString/g, '.binary_to_string' ],
[ /\.implodeParams/g, '.implode_params'],
[ /\.extractParams/g, '.extract_params'],
[ /\.indexBy/g, '.index_by'],
[ /\.sortBy/g, '.sort_by'],
[ /\.marketIds/g, '.market_ids'],
[ /\.marketId/g, '.market_id'],
[ /\.fetchOrderStatus/g, '.fetch_order_status'],
[ /\.fetchOpenOrders/g, '.fetch_open_orders'],
[ /\.parseOrders/g, '.parse_orders'],
[ /\.parseOrder/g, '.parse_order'],
[ /\.loadMarkets/g, '.load_markets'],
[ /\.encodeURIComponent/g, '.encode_uri_component'],
[ /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/g, '$1' ],
[ /Object\.keys\s*\((.*)\)\.length/g, '$1' ],
[ /Object\.keys\s*\((.*)\)/g, 'array_keys ($1)' ],
[ /([^\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)'],
[ /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, 'count ($1);' ],
[ /([^\s]+)\.length/g, 'strlen ($1)' ],
[ /\.push\s*\(([\s\S]+?)\)\;/g, '[] = $1;' ],
[ /(\s)await(\s)/g, '$1' ],
[ /([\S])\: /g, '$1 => ' ],
[ /\{([^\;\{]+?)\}([^\s])/g, 'array ($1)$2' ],
[ /\[\s*([^\]]+?)\s*\]\.join\s*\(\s*([^\)]+?)\s*\)/g, "implode ($2, array ($1))" ],
[ /\[\s([^\]]+?)\s\]/g, 'array ($1)' ],
[ /JSON\.stringify/g, 'json_encode' ],
[ /\'([^\']+)\'\.sprintf\s*\(([^\)]+)\)/g, "sprintf ('$1', $2)" ],
[ /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)' ],
[ /([^\(\s]+)\s+%\s+([^\s\)]+)/g, 'fmod ($1, $2)' ],
[ /([^\s]+)\.indexOf\s*\(([^\)]+)\)\s*\>\=\s*0/g, 'mb_strpos ($1, $2) !== false' ],
[ /([^\s]+)\.indexOf\s*\(([^\)]+)\)/g, 'mb_strpos ($1, $2)' ],
[ /\(([^\s]+)\sin\s([^\)]+)\)/g, '(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'],
]
let pyRegexSync = pyRegex.concat ([
[ /(\s)await(\s)/g, '$1' ]
])
let pyBody = regexAll (body, pyRegexSync)
let pyBodyAsync = regexAll (body, pyRegex)
// special case for Python OrderedDicts
let orderedRegex = /\.ordered\s+\(\{([^\}]+)\}\)/g
let orderedMatches = undefined
while (orderedMatches = orderedRegex.exec (pyBody)) {
let replaced = orderedMatches[1].replace (/^(\s+)([^\:]+)\:\s*([^\,]+)\,$/gm, '$1($2, $3),')
pyBody = pyBody.replace (orderedRegex, '\.ordered ([' + replaced + '])')
}
py.push ('');
py.push (' def ' + method + '(self' + (pyArgs.length ? ', ' + pyArgs.replace (/undefined/g, 'None') : '') + '):');
py.push (pyBody);
pyAsync.push ('');
pyAsync.push (' ' + keyword + 'def ' + method + '(self' + (pyArgs.length ? ', ' + pyArgs.replace (/undefined/g, 'None') : '') + '):');
pyAsync.push (pyBodyAsync);
let phBody = regexAll (body, phRegex.concat (phVarsRegex))
ph.push ('');
ph.push (' public function ' + method + ' (' + phArgs.replace (/undefined/g, 'null').replace ('{}', 'array ()') + ') {');
ph.push (phBody);
ph.push (' }')
}
py.push ('')
pyAsync.push ('')
python.push (py.join ("\n"))
pythonAsync.push (pyAsync.join ("\n"))
ph.push ('}')
ph.push ('')
php.push (ph.join ("\n"))
}
//-----------------------------------------------------------------------------
function transpile (oldName, newName, content, comment = '//') {
log.bright.cyan ('Transpiling ' + oldName.yellow + ' → ' + newName.yellow)
let fileContents = fs.readFileSync (oldName, 'utf8')
fileContents = fileContents.split ("\n" + comment + "====") [0]
fileContents +=
"\n" + comment + "==============================================================================\n" +
content.join ("\n" + comment + "------------------------------------------------------------------------------\n")
fs.truncateSync (newName)
fs.writeFileSync (newName, fileContents)
}
//-----------------------------------------------------------------------------
function copyFile (oldName, newName) {
let contents = fs.readFileSync (oldName, 'utf8')
fs.truncateSync (newName)
fs.writeFileSync (newName, contents)
}
//-----------------------------------------------------------------------------
transpile ('./ccxt/exchanges.py', './ccxt/exchanges.py', python, '#')
transpile ('./ccxt/async/exchanges.py', './ccxt/async/exchanges.py', pythonAsync, '#')
transpile ('./ccxt.php', './build/ccxt.php', php, '//')
//-----------------------------------------------------------------------------
log.bright.green ('Transpiled successfully.')
;