UNPKG

sfccxt

Version:

A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges

966 lines (871 loc) 92.9 kB
// --------------------------------------------------------------------------- // Usage: npm run transpile // --------------------------------------------------------------------------- "use strict"; const fs = require ('fs') , { promisify } = require("util") , log = require ('ololog').unlimited , _ = require ('ansicolor').nice , errors = require ('../js/base/errors.js') , functions = require ('../js/base/functions.js') , { fork } = require ('child_process') , os = require ('os') , { unCamelCase, precisionConstants, safeString, } = functions , { basename } = require ('path') , { createFolderRecursively, replaceInFile, overwriteFile, } = require ('./fs.js') , baseExchangeJsFile = './js/base/Exchange.js' , Exchange = require ('.' + baseExchangeJsFile) , tsFilename = './ccxt.d.ts' , pythonCodingUtf8 = '# -*- coding: utf-8 -*-' const exchangeIds = require("../exchanges.json").ids; class Transpiler { getCommonRegexes () { return [ [ /\.deepExtend\s/g, '.deep_extend'], [ /\.safeFloat2\s/g, '.safe_float_2'], [ /\.safeInteger2\s/g, '.safe_integer_2'], [ /\.safeIntegerProduct2\s/g, '.safe_integer_product_2'], [ /\.safeTimestamp2\s/g, '.safe_timestamp_2'], [ /\.safeString2\s/g, '.safe_string_2'], [ /\.safeNumber2\s/g, '.safe_number_2'], [ /\.safeStringLower2\s/g, '.safe_string_lower_2'], [ /\.safeStringUpper2\s/g, '.safe_string_upper_2'], [ /\.safeValue2\s/g, '.safe_value_2'], [ /\.safeNumber\s/g, '.safe_number'], [ /\.safeFloat\s/g, '.safe_float'], [ /\.safeInteger\s/g, '.safe_integer'], [ /\.safeIntegerProduct\s/g, '.safe_integer_product'], [ /\.safeTimestamp\s/g, '.safe_timestamp'], [ /\.safeString\s/g, '.safe_string'], [ /\.safeStringLower\s/g, '.safe_string_lower'], [ /\.safeStringUpper\s/g, '.safe_string_upper'], [ /\.safeValue\s/g, '.safe_value'], [ /\.safeFloatN\s/g, '.safe_float_n'], [ /\.safeIntegerN\s/g, '.safe_integer_n'], [ /\.safeIntegerProductN\s/g, '.safe_integer_product_n'], [ /\.safeTimestampN\s/g, '.safe_timestamp_n'], [ /\.safeStringN\s/g, '.safe_string_n'], [ /\.safeNumberN\s/g, '.safe_number_n'], [ /\.safeStringLowerN\s/g, '.safe_string_lower_n'], [ /\.safeStringUpperN\s/g, '.safe_string_upper_n'], [ /\.safeValueN\s/g, '.safe_value_n'], [ /\.inArray\s/g, '.in_array'], [ /\.toArray\s/g, '.to_array'], [ /\.isEmpty\s/g, '.is_empty'], [ /\.arrayConcat\s/g, '.array_concat'], [ /\.binaryConcat\s/g, '.binary_concat'], [ /\.binaryConcatArray\s/g, '.binary_concat_array'], [ /\.binaryToString\s/g, '.binary_to_string' ], [ /\.precisionFromString\s/g, '.precision_from_string'], [ /\.parsePrecision\s/g, '.parse_precision'], [ /\.parseNumber\s/g, '.parse_number'], [ /\.implodeHostname\s/g, '.implode_hostname'], [ /\.implodeParams\s/g, '.implode_params'], [ /\.extractParams\s/g, '.extract_params'], [ /\.safeBalance\s/g, '.safe_balance'], [ /\.parseAccounts\s/g, '.parse_accounts' ], [ /\.parseAccount\s/g, '.parse_account' ], [ /\.parseBalance\s/g, '.parse_balance'], [ /\.parseBorrowInterest\s/g, '.parse_borrow_interest'], [ /\.parseFundingRateHistories\s/g, '.parse_funding_rate_histories'], [ /\.parseFundingRateHistory\s/g, '.parse_funding_rate_history'], [ /\.parseOHLCVs\s/g, '.parse_ohlcvs'], [ /\.parseOHLCV\s/g, '.parse_ohlcv'], [ /\.parseDate\s/g, '.parse_date'], [ /\.parseDepositAddresses\s/g, '.parse_deposit_addresses'], [ /\.parseDepositAddress\s/g, '.parse_deposit_address'], [ /\.parseMarketLeverageTiers\s/g, '.parse_market_leverage_tiers'], [ /\.parseLeverageTiers\s/g, '.parse_leverage_tiers'], [ /\.parseLedgerEntry\s/g, '.parse_ledger_entry'], [ /\.parseLedger\s/g, '.parse_ledger'], [ /\.parseTickers\s/g, '.parse_tickers'], [ /\.parseTicker\s/g, '.parse_ticker'], [ /\.parseTimeframe\s/g, '.parse_timeframe'], [ /\.parseTradesData\s/g, '.parse_trades_data'], [ /\.parseTrades\s/g, '.parse_trades'], [ /\.parseTrade\s/g, '.parse_trade'], [ /\.parseTradingFees\s/g, '.parse_trading_fees'], [ /\.parseTradingFee\s/g, '.parse_trading_fee'], [ /\.parseTradingViewOHLCV\s/g, '.parse_trading_view_ohlcv'], [ /\.convertTradingViewToOHLCV\s/g, '.convert_trading_view_to_ohlcv'], [ /\.parseTransactions\s/g, '.parse_transactions'], [ /\.parseTransaction\s/g, '.parse_transaction'], [ /\.parseTransfers\s/g, '.parse_transfers'], [ /\.parseTransfer\s/g, '.parse_transfer'], [ /\.parseOrderBook\s/g, '.parse_order_book'], [ /\.parseBidsAsks\s/g, '.parse_bids_asks'], [ /\.parseBidAsk\s/g, '.parse_bid_ask'], [ /\.parseOpenInterests\s/g, '.parse_open_interests'], [ /\.parseOpenInterest\s/g, '.parse_open_interest'], [ /\.parseBidAsk\s/g, '.parse_bid_ask'], [ /\.parseOrders\s/g, '.parse_orders'], [ /\.parseOrderStatus\s/g, '.parse_order_status'], [ /\.parseOrder\s/g, '.parse_order'], [ /\.parseJson\s/g, '.parse_json'], [ /\.parseAccountPosition\s/g, '.parse_account_position' ], [ /\.parsePositionRisk\s/g, '.parse_position_risk' ], [ /\.parsePositions\s/g, '.parse_positions' ], [ /\.parsePosition\s/g, '.parse_position' ], [ /\.parseIncome\s/g, '.parse_income' ], [ /\.parseIncomes\s/g, '.parse_incomes' ], [ /\.parseFundingRates\s/g, '.parse_funding_rates' ], [ /\.parseFundingRate\s/g, '.parse_funding_rate' ], [ /\.parseMarginModification\s/g, '.parse_margin_modification' ], [ /\.filterByArray\s/g, '.filter_by_array'], [ /\.filterByValueSinceLimit\s/g, '.filter_by_value_since_limit'], [ /\.filterBySymbolSinceLimit\s/g, '.filter_by_symbol_since_limit'], [ /\.filterByCurrencySinceLimit\s/g, '.filter_by_currency_since_limit'], [ /\.filterBySinceLimit\s/g, '.filter_by_since_limit'], [ /\.filterBySymbol\s/g, '.filter_by_symbol'], [ /\.getVersionString\s/g, '.get_version_string'], [ /\.indexBy\s/g, '.index_by'], [ /\.sortBy\s/g, '.sort_by'], [ /\.sortBy2\s/g, '.sort_by_2'], [ /\.filterBy\s/g, '.filter_by'], [ /\.groupBy\s/g, '.group_by'], [ /\.marketSymbols\s/g, '.market_symbols'], [ /\.marketIds\s/g, '.market_ids'], [ /\.marketId\s/g, '.market_id'], [ /\.fetchFundingFee\s/g, '.fetch_funding_fee'], [ /\.fetchFundingFees\s/g, '.fetch_funding_fees'], [ /\.fetchTradingLimits\s/g, '.fetch_trading_limits'], [ /\.fetchTransactionFee\s/g, '.fetch_transaction_fee'], [ /\.fetchTransactionFees\s/g, '.fetch_transaction_fees'], [ /\.fetchTradingFees\s/g, '.fetch_trading_fees'], [ /\.fetchTradingFee\s/g, '.fetch_trading_fee'], [ /\.fetchFees\s/g, '.fetch_fees'], [ /\.fetchOHLCVC\s/g, '.fetch_ohlcvc'], [ /\.fetchOHLCV\s/g, '.fetch_ohlcv'], [ /\.buildOHLCVC\s/g, '.build_ohlcvc'], [ /\.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'], [ /\.fetchOpenOrder\s/g, '.fetch_open_order'], [ /\.fetchOrders\s/g, '.fetch_orders'], [ /\.fetchOrderTrades\s/g, '.fetch_order_trades'], [ /\.fetchOrder\s/g, '.fetch_order'], [ /\.fetchBalance\s/g, '.fetch_balance'], [ /\.fetchTotalBalance\s/g, '.fetch_total_balance'], [ /\.fetchUsedBalance\s/g, '.fetch_used_balance'], [ /\.fetchFreeBalance\s/g, '.fetch_free_balance'], [ /\.fetchPartialBalance\s/g, '.fetch_partial_balance'], [ /\.fetchPermissions\s/g, '.fetch_permissions'], [ /\.fetchBidsAsks\s/g, '.fetch_bids_asks'], [ /\.fetchTickers\s/g, '.fetch_tickers'], [ /\.fetchTicker\s/g, '.fetch_ticker'], [ /\.fetchCurrencies\s/g, '.fetch_currencies'], [ /\.fetchStatus\s/g, '.fetch_status'], [ /\.numberToString\s/g, '.number_to_string' ], [ /\.decimalToPrecision\s/g, '.decimal_to_precision'], [ /\.priceToPrecision\s/g, '.price_to_precision'], [ /\.amountToPrecision\s/g, '.amount_to_precision'], [ /\.amountToLots\s/g, '.amount_to_lots'], [ /\.feeToPrecision\s/g, '.fee_to_precision'], [ /\.currencyToPrecision\s/g, '.currency_to_precision'], [ /\.costToPrecision\s/g, '.cost_to_precision'], [ /\.commonCurrencyCode\s/g, '.common_currency_code'], [ /\.getDefaultOptions\s/g, '.get_default_options'], [ /\.loadAccounts\s/g, '.load_accounts'], [ /\.fetchAccounts\s/g, '.fetch_accounts'], [ /\.loadFees\s/g, '.load_fees'], [ /\.loadMarkets\s/g, '.load_markets'], [ /\.loadTimeDifference\s/g, '.load_time_difference'], [ /\.fetchMarkets\s/g, '.fetch_markets'], [ /\.fetchMarketLeverageTiers\s/g, '.fetch_market_leverage_tiers'], [ /\.fetchLeverageTiers\s/g, '.fetch_leverage_tiers'], [ /\.appendInactiveMarkets\s/g, '.append_inactive_markets'], [ /\.fetchCategories\s/g, '.fetch_categories'], [ /\.calculateFee\s/g, '.calculate_fee'], [ /\.createOrder\s/g, '.create_order'], [ /\.createPostOnlyOrder\s/g, '.create_post_only_order'], [ /\.createStopOrder\s/g, '.create_stop_order'], [ /\.createStopLimitOrder\s/g, '.create_stop_limit_order'], [ /\.createStopMarketOrder\s/g, '.create_stop_market_order'], [ /\.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'], [ /\.throwExceptionOnError\s/g, '.throw_exception_on_error'], [ /\.handleErrors\s/g, '.handle_errors'], [ /\.handleWithdrawTagAndParams\s/g, '.handle_withdraw_tag_and_params'], [ /\.checkRequiredCredentials\s/g, '.check_required_credentials'], [ /\.checkRequiredDependencies\s/g, '.check_required_dependencies'], [ /\.checkAddress\s/g, '.check_address'], [ /\.convertTradingViewToOHLCV\s/g, '.convert_trading_view_to_ohlcv'], [ /\.convertOHLCVToTradingView\s/g, '.convert_ohlcv_to_trading_view'], [ /\.signBodyWithSecret\s/g, '.sign_body_with_secret'], [ /\.isJsonEncodedObject\s/g, '.is_json_encoded_object'], [ /\.setSandboxMode\s/g, '.set_sandbox_mode'], [ /\.safeCurrencyCode\s/g, '.safe_currency_code'], [ /\.safeCurrency\s/g, '.safe_currency'], [ /\.safeSymbol\s/g, '.safe_symbol'], [ /\.safeMarket\s/g, '.safe_market'], [ /\.safeOrder\s/g, '.safe_order'], [ /\.safeTicker\s/g, '.safe_ticker'], [ /\.roundTimeframe\s/g, '.round_timeframe'], [ /\.calculateRateLimiterCost\s/g, '.calculate_rate_limiter_cost' ], [ /\.findBroadlyMatchedKey\s/g, '.find_broadly_matched_key' ], [ /\.throwBroadlyMatchedException\s/g, '.throw_broadly_matched_exception' ], [ /\.throwExactlyMatchedException\s/g, '.throw_exactly_matched_exception' ], [ /\.getNetwork\s/g, '.get_network' ], [ /\.findTimeframe\s/g, '.find_timeframe'], [ /errorHierarchy/g, 'error_hierarchy'], [ /\.base16ToBinary/g, '.base16_to_binary'], [ /\'use strict\';?\s+/g, '' ], [ /\.urlencodeNested\s/g, '.urlencode_nested' ], [ /\.urlencodeWithArrayRepeat\s/g, '.urlencode_with_array_repeat' ], [ /\.call\s*\(this, /g, '(' ], [ /\.getSupportedMapping\s/g, '.get_supported_mapping'], [ /\.fetchBorrowRates\s/g, '.fetch_borrow_rates'], [ /\.fetchBorrowRate\s/g, '.fetch_borrow_rate'], [ /\.handleMarketTypeAndParams\s/g, '.handle_market_type_and_params'], [ /\.checkOrderArguments\s/g, '.check_order_arguments'], [ /\.isPostOnly\s/g, '.is_post_only'], [ /\.reduceFeesByCurrency\s/g, '.reduce_fees_by_currency'], [ /\.omitZero\s/g, '.omit_zero'], ] } getPythonRegexes () { return [ [ /Array\.isArray\s*\(([^\)]+)\)/g, 'isinstance($1, list)' ], [ /([^\(\s]+)\s+instanceof\s+String/g, 'isinstance($1, str)' ], [ /([^\(\s]+)\s+instanceof\s+([^\)\s]+)/g, 'isinstance($1, $2)' ], [ /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+\'undefined\'/g, '$1 is None' ], [ /typeof\s+(.+?)\s+\!\=\=?\s+\'undefined\'/g, '$1 is not None' ], [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'number\'/g, "isinstance($1[$2], numbers.Real)" ], [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'number\'/g, "(not isinstance($1[$2], numbers.Real))" ], [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'number\'/g, "isinstance($1, numbers.Real)" ], [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'number\'/g, "(not isinstance($1, numbers.Real))" ], [ /([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+undefined/g, '$1[$2] is None' ], [ /([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+undefined/g, '$1[$2] is not None' ], [ /([^\s]+)\s+\=\=\=?\s+undefined/g, '$1 is None' ], [ /([^\s]+)\s+\!\=\=?\s+undefined/g, '$1 is not None' ], [ /(.+?)\s+\=\=\=?\s+undefined/g, '$1 is None' ], [ /(.+?)\s+\!\=\=?\s+undefined/g, '$1 is not None' ], // // too broad, have to rewrite these cause they don't work // // [ /([^\s]+)\s+\=\=\=?\s+true/g, 'isinstance($1, bool) and ($1 is True)' ], // [ /([^\s]+)\s+\!\=\=?\s+true/g, 'isinstance($1, bool) and ($1 is not True)' ], // [ /([^\s]+)\s+\=\=\=?\s+false/g, 'isinstance($1, bool) and ($1 is False)' ], // [ /([^\s]+)\s+\!\=\=?\s+false/g, 'isinstance($1, bool) and ($1 is not False)' ], [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'string\'/g, 'isinstance($1[$2], str)' ], [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'string\'/g, 'not isinstance($1[$2], str)' ], [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'string\'/g, 'isinstance($1, str)' ], [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'string\'/g, 'not isinstance($1, str)' ], [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'object\'/g, 'isinstance($1[$2], dict)' ], [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'object\'/g, 'not isinstance($1[$2], dict)' ], [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'object\'/g, 'isinstance($1, dict)' ], [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'object\'/g, 'not isinstance($1, dict)' ], [ /undefined/g, 'None' ], [ /\=\=\=?/g, '==' ], [ /\!\=\=?/g, '!=' ], [ /this\.stringToBinary\s*\((.*)\)/g, '$1' ], [ /\.shift\s*\(\)/g, '.pop(0)' ], [ /Number\.MAX_SAFE_INTEGER/g, 'float(\'inf\')'], [ /function\s*(\w+\s*\([^)]+\))\s*{/g, 'def $1:'], // [ /\.replaceAll\s*\(([^)]+)\)/g, '.replace($1)' ], // still not a part of the standard [ /assert\s*\((.+)\);/g, 'assert $1'], [ /Promise\.all\s*\(([^\)]+)\)/g, 'asyncio.gather(*$1)' ], [ /Precise\.stringAdd\s/g, 'Precise.string_add' ], [ /Precise\.stringMul\s/g, 'Precise.string_mul' ], [ /Precise\.stringDiv\s/g, 'Precise.string_div' ], [ /Precise\.stringSub\s/g, 'Precise.string_sub' ], [ /Precise\.stringAbs\s/g, 'Precise.string_abs' ], [ /Precise\.stringNeg\s/g, 'Precise.string_neg' ], [ /Precise\.stringMod\s/g, 'Precise.string_mod' ], [ /Precise\.stringEquals\s/g, 'Precise.string_equals' ], [ /Precise\.stringEq\s/g, 'Precise.string_eq' ], [ /Precise\.stringMin\s/g, 'Precise.string_min' ], [ /Precise\.stringMax\s/g, 'Precise.string_max' ], [ /Precise\.stringGt\s/g, 'Precise.string_gt' ], [ /Precise\.stringGe\s/g, 'Precise.string_ge' ], [ /Precise\.stringLt\s/g, 'Precise.string_lt' ], [ /Precise\.stringLe\s/g, 'Precise.string_le' ], [ /\.padEnd\s/g, '.ljust'], [ /\.padStart\s/g, '.rjust' ], // insert common regexes in the middle (critical) ].concat (this.getCommonRegexes ()).concat ([ // [ /this\.urlencode\s/g, '_urlencode.urlencode ' ], // use self.urlencode instead [ /this\./g, 'self.' ], [ /([^a-zA-Z\'])this([^a-zA-Z])/g, '$1self$2' ], [ /\[\s*([^\]]+)\s\]\s=/g, '$1 =' ], [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s\[\s*([^\]]+)\s\]/g, '$1$2' ], [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s\{\s*([^\}]+)\s\}\s\=\s([^\;]+)/g, '$1$2 = (lambda $2: ($2))(**$3)' ], [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s/g, '$1' ], [ /Object\.keys\s*\((.*)\)\.length/g, '$1' ], [ /Object\.keys\s*\((.*)\)/g, 'list($1.keys())' ], [ /Object\.values\s*\((.*)\)/g, 'list($1.values())' ], [ /\[([^\]]+)\]\.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' ], [ /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)):'], [ /for\s+\(([a-zA-Z0-9_]+)\s*=\s*([^\;\s]+\s*)\;[^\<\>\=]+(?:\<=|\>=|<|>)\s*(.*)\s*\;[^\)]+\)\s*{/g, 'for $1 in range($2, $3):'], [ /\s\|\|\s/g, ' or ' ], [ /\s\&\&\s/g, ' and ' ], [ /\!([^\s\='"])/g, 'not $1'], [ /\.push\s*\(([\s\S]+?)\);/g, '.append($1);' ], [ /^(\s*}\s*$)+/gm, '' ], [ /\;(\s+?\/\/.+?)/g, '$1' ], [ /\;$/gm, '' ], [ /\.toUpperCase\s*/g, '.upper' ], [ /\.toLowerCase\s*/g, '.lower' ], [ /(\b)String(\b)/g, '$1str$2'], [ /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, "format($1, '.$2f')" ], [ /([^\s]+)\.toFixed\s*\(([^\)]+)\)/g, "format($1, '.' + str($2) + 'f')" ], [ /parseFloat\s*/g, 'float'], [ /parseInt\s*/g, 'int'], [ /self\[([^\]+]+)\]/g, 'getattr(self, $1)' ], [ /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(math.ceil($1))' ], [ /Math\.log/g, 'math.log' ], [ /([a-zA-Z0-9_\.]*\([^\)]+\)|[^\s]+)\s+\?\s*([^\:]+)\s+\:\s*([^\n]+)/g, '$2 if $1 else $3'], [ /([^\s]+)\.slice \(([^\,\)]+)\,\s?([^\)]+)\)/g, '$1[$2:$3]' ], [ /([^\s]+)\.slice \(([^\)\:]+)\)/g, '$1[$2:]' ], [ /([^\s(:]+)\.length/g, 'len($1)' ], [ /(^|\s)\/\//g, '$1#' ], [ /([^\n\s]) #/g, '$1 #' ], // PEP8 E261 [ /\.indexOf/g, '.find'], [ /(\s|\()true/g, '$1True'], [ /(\s|\()false/g, '$1False'], [ /([^\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' ], [ / = new /g, ' = ' ], // python does not have a 'new' keyword [ /console\.log\s/g, 'print' ], [ /process\.exit\s+/g, 'sys.exit' ], [ /(while \(.*\)) {/, '$1\:' ], // While loops replace bracket with : [ /([^:+=\/\*\s-]+) \(/g, '$1(' ], // PEP8 E225 remove whitespaces before left ( round bracket [ /\sand\(/g, ' and (' ], [ /\sor\(/g, ' or (' ], [ /\snot\(/g, ' not (' ], [ /\[ /g, '[' ], // PEP8 E201 remove whitespaces after left [ square bracket [ /\{ /g, '{' ], // PEP8 E201 remove whitespaces after left { bracket [ /(?<=[^\s#]) \]/g, ']' ], // PEP8 E202 remove whitespaces before right ] square bracket [ /(?<=[^\s#]) \}/g, '}' ], // PEP8 E202 remove whitespaces before right } bracket [ /([^a-z])(elif|if|or|else)\(/g, '$1$2 \(' ], // a correction for PEP8 E225 side-effect for compound and ternary conditionals [ /\!\=\sTrue/g, 'is not True' ], // a correction for PEP8 E712, it likes "is not True", not "!= True" [ /\=\=\sTrue/g, 'is True' ], // a correction for PEP8 E712, it likes "is True", not "== True" [ /\sdelete\s/g, ' del ' ], [ /(?<!#.+)null/, 'None' ], [ /\/\*\*/, '\"\"\"' ], // Doc strings [ / \*\//, '\"\"\"' ], // Doc strings [ /\[([^\[\]]*)\]\{@link (.*)\}/g, '`$1 <$2>`' ], // docstring item with link [ /\s+\* @method/g, '' ], // docstring @method [ /(\s+) \* @description (.*)/g, '$1$2' ], // docstring description [ /\s+\* @name .*/g, '' ], // docstring @name [ /(\s+) \* @see( .*)/g, '$1see$2' ], // docstring @see [ /(\s+ \* @(param|returns) {[^}]*)string([^}]*}.*)/g, '$1str$3' ], // docstring type conversion [ /(\s+ \* @(param|returns) {[^}]*)object([^}]*}.*)/g, '$1dict$3' ], // doctstrubg type conversion [ /(\s+) \* @returns ([^\{])/g, '$1:returns: $2' ], // docstring return [ /(\s+) \* @returns \{(.+)\}/g, '$1:returns $2:' ], // docstring return [ /(\s+ \* @param \{[\]\[\|a-zA-Z]+\} )([a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+) (.*)/g, '$1$2[\'$3\'] $4' ], // docstring params.anything [ /(\s+) \* @([a-z]+) \{([\]\[a-zA-Z\|]+)\} ([a-zA-Z0-9_\-\.\[\]\']+)/g, '$1:$2 $3 $4:' ], // docstring param ]) } getPython2Regexes () { return [ [ /.+asyncio\.gather.+\n/g, '' ], // remove line entirely [ /(\s)await(\s)/g, '$1' ] ] } getPHPSyncRegexes () { return [ // delete await, the following regex does not pick up multiline await calls [ /\bAsync\\await\((.+)\);/g, '$1;' ], // hence the following regex is added with a dotAll modifier 's' // and a non greedy match for the calls not picked up by the previous regex [ /\bAsync\\await\((.+?)\);/gs, '$1;' ], [ /.+Promise\\all.+\n/g, '' ], // remove line entirely [ /\byield(?: from)?\s+/g, '' ], // delete yield from ] } getPHPRegexes () { return [ // // Curly-braces are used for both dictionaries in the code as well as for the url-imploded params. // For example: https://docs.ccxt.com/en/latest/manual.html#implicit-api-methods // // There's a conflict between the curly braces that have to be converted from dictionaries to PHP-arrays and // the curly braces used for url-imploded params that should not be touched. // // The transpiler takes all non-spaced strings in curly braces {likeThis} and converts them to ~likeThis~. // That is done to avoid changing the curly braces into the array() in PHP. // This way we protect the url-imploded params from being touched by the regexes that will follow. // That conversion is done first-thing, at the very early stage of transpilation. // The regexes are applied in the order they're listed, top-down. // // A dictionary in curly braces will never have those curly braces attached to the contents of the dictionary. // There will always be a space like { 'a': b, 'c': d }. // Hence, the remaining non-converted curly-brace dictionaries will have to be converted to arrays in PHP. // That is done in the middle of the transpilation process. // // The last step is to convert those "saved embedded/imploded url-params substitutions" from ~likeThis~ back to {likeThis}. // That is done at the very last regex steps. // All of that is a workaround for PHP-arrays vs dictionaries vs url-imploded params in other langs. // [ /\{([\]\[\|a-zA-Z0-9_-]+?)\}/g, '~$1~' ], // resolve the "arrays vs url params" conflict (both are in {}-brackets) [ /\[([^\]\[]*)\]\{(@link .*)\}/g, '~$2 $1~' ], // docstring item with link [ /\s+\* @method/g, '' ], // docstring @method [ /(\s+)\* @description (.*)/g, '$1\* $2' ], // docstring description [ /\s+\* @name .*/g, '' ], // docstring @name [ /(\s+)\* @returns/g, '$1\* @return' ], // docstring return [ /\!Array\.isArray\s*\(([^\)]+)\)/g, "gettype($1) !== 'array' || array_keys($1) !== array_keys(array_keys($1))" ], [ /Array\.isArray\s*\(([^\)]+)\)/g, "gettype($1) === 'array' && array_keys($1) === array_keys(array_keys($1))" ], [ /([^\(\s]+)\s+instanceof\s+String/g, 'is_string($1)' ], [ /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+\'undefined\'/g, '$1 === null' ], [ /typeof\s+(.+?)\s+\!\=\=?\s+\'undefined\'/g, '$1 !== null' ], [ /([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+undefined/g, '$1[$2] === null' ], [ /([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+undefined/g, '$1[$2] !== null' ], [ /([^\s]+)\s+\=\=\=?\s+undefined/g, '$1 === null' ], [ /([^\s]+)\s+\!\=\=?\s+undefined/g, '$1 !== null' ], [ /(.+?)\s+\=\=\=?\s+undefined/g, '$1 === null' ], [ /(.+?)\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'" ], [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'object\'/g, "gettype($1[$2]) === 'array'" ], [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'object\'/g, "gettype($1[$2]) !== 'array'" ], [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'object\'/g, "gettype($1) === 'array'" ], [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'object\'/g, "gettype($1) !== 'array'" ], [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'number\'/g, "(is_float($1[$2]) || is_int($1[$2]))" ], // same as above but for number [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'number\'/g, "!(is_float($1[$2]) || is_int($1[$2]))" ], [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'number\'/g, "(is_float($1) || is_int($1))" ], [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'number\'/g, "!(is_float($1) || is_int($1))" ], [ /undefined/g, 'null' ], [ /\} else if/g, '} elseif' ], [ /this\.extend\s/g, 'array_merge' ], [ /this\.stringToBinary\s*\((.*)\)/g, '$1' ], [ /this\.stringToBase64\s/g, 'base64_encode' ], [ /this\.binaryToBase16\s/g, 'bin2hex' ], [ /this\.base64ToBinary\s/g, 'base64_decode' ], [ /this\.base64ToString\s/g, 'base64_decode' ], [ /Promise\.all\s*\(([^\)]+)\)/g, 'Promise\\all($1)' ], // deepExtend is commented for PHP because it does not overwrite linear arrays // a proper \ccxt\Exchange::deep_extend() base method is implemented instead // [ /this\.deepExtend\s/g, 'array_replace_recursive'], [ /(\w+)\.shift\s*\(\)/g, 'array_shift($1)' ], [ /(\w+)\.pop\s*\(\)/g, 'array_pop($1)' ], [ /Number\.MAX_SAFE_INTEGER/g, 'PHP_INT_MAX' ], [ /Precise\.stringAdd\s/g, 'Precise::string_add' ], [ /Precise\.stringDiv\s/g, 'Precise::string_div' ], [ /Precise\.stringMul\s/g, 'Precise::string_mul' ], [ /Precise\.stringSub\s/g, 'Precise::string_sub' ], [ /Precise\.stringAbs\s/g, 'Precise::string_abs' ], [ /Precise\.stringNeg\s/g, 'Precise::string_neg' ], [ /Precise\.stringMod\s/g, 'Precise::string_mod' ], [ /Precise\.stringEquals\s/g, 'Precise::string_equals' ], [ /Precise\.stringEq\s/g, 'Precise::string_eq' ], [ /Precise\.stringMin\s/g, 'Precise::string_min' ], [ /Precise\.stringMax\s/g, 'Precise::string_max' ], [ /Precise\.stringGt\s/g, 'Precise::string_gt' ], [ /Precise\.stringGe\s/g, 'Precise::string_ge' ], [ /Precise\.stringLt\s/g, 'Precise::string_lt' ], [ /Precise\.stringLe\s/g, 'Precise::string_le' ], [ /(\w+)\.padEnd\s*\(([^,]+),\s*([^)]+)\)/g, 'str_pad($1, $2, $3, STR_PAD_RIGHT)' ], [ /(\w+)\.padStart\s*\(([^,]+),\s*([^)]+)\)/g, 'str_pad($1, $2, $3, STR_PAD_LEFT)' ], // insert common regexes in the middle (critical) ].concat (this.getCommonRegexes ()).concat ([ [ /this\./g, '$this->' ], [ / this;/g, ' $this;' ], [ /([^'])this_\./g, '$1$this_->' ], [ /([^'])\{\}/g, '$1array()' ], [ /([^'])\[\]/g, '$1array()' ], // add {}-array syntax conversions up to 20 levels deep on the same line ]).concat ([ ... Array (20) ].map (x => [ /\{([^\n\}]+)\}/g, 'array($1)' ] )).concat ([ [ /\[\s*([^\]]+)\s\]\s=/g, 'list($1) =' ], [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s\[\s*([^\]]+)\s\]/g, '$1list($2)' ], [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s\{\s*([^\}]+)\s\}/g, '$1array_values(list($2))' ], [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s/g, '$1' ], [ /Object\.keys\s*\((.*)\)\.length/g, '$1' ], [ /Object\.keys\s*\((.*)\)/g, 'is_array($1) ? array_keys($1) : array()' ], [ /Object\.values\s*\((.*)\)/g, 'is_array($1) ? array_values($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;' ], [ '([^a-z]+) (' + Object.keys (errors).join ('|') + ')([^\\s])', "$1 '\\\\ccxt\\\\$2'$3" ], [ /\}\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) {' ], [ /for\s+\(([a-zA-Z0-9_]+)\s*=\s*([^\;\s]+\s*)\;[^\<\>\=]+(\<=|\>=|<|>)\s*(.*)\s*\;([^\)]+)\)\s*{/g, 'for ($1 = $2; $1 $3 $4;$5) {' ], [ /([^\s]+)\.length\;/g, 'count($1);' ], [ /\.push\s*\(([\s\S]+?)\)\;/g, '[] = $1;' ], [ /\sawait\s+([^;]+);/g, ' Async\\await($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 ([ [ /(\b)String(\b)/g, "$1'strval'$2"], [ /JSON\.stringify/g, 'json_encode' ], [ /JSON\.parse\s+\(([^\)]+)\)/g, 'json_decode($1, $$as_associative_array = true)' ], // [ /\'([^\']+)\'\.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'], [ / \+ (?!\d)/g, ' . ' ], [ / \+\= (?!\d)/g, ' .= ' ], [ /([^\s\(]+(?:\s*\(.+\))?)\.toUpperCase\s*\(\)/g, 'strtoupper($1)' ], [ /([^\s\(]+(?:\s*\(.+\))?)\.toLowerCase\s*\(\)/g, 'strtolower($1)' ], // [ /([^\s\(]+(?:\s*\(.+\))?)\.replaceAll\s*\(([^)]+)\)/g, 'str_replace($2, $1)' ], // still not a part of the standard in Node.js 13 [ /([^\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 - $2)' ], [ /([^\s\(]+).split \(('[^']*'|[^\,]+?)\)/g, 'explode($2, $1)' ], [ /([^\s\(]+)\.length/g, 'strlen($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*\(([^\)]+)\)\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)' ], [ 'new ccxt\\.', 'new \\ccxt\\' ], // a special case for test_exchange_datetime_functions.php (and for other files, maybe) [ /Math\.(max|min)/g, '$1' ], [ /console\.log/g, 'var_dump'], [ /process\.exit/g, 'exit'], [ /super\./g, 'parent::'], [ /\sdelete\s([^\n]+)\;/g, ' unset($1);' ], [ /\~([\]\[\|@\.\s+\:\/#\-a-zA-Z0-9_-]+?)\~/g, '{$1}' ], // resolve the "arrays vs url params" conflict (both are in {}-brackets) [ /(\s+ \* @(param|return) {[^}]*)object([^}]*}.*)/g, '$1array$3' ], // docstring type conversion ]) } getBaseClass () { return new Exchange () } getBaseMethods () { const baseExchange = this.getBaseClass () let object = baseExchange let properties = [] while (object !== Object.prototype) { properties = properties.concat (Object.getOwnPropertyNames (object)) object = Object.getPrototypeOf (object) } return properties.filter (x => typeof baseExchange[x] === 'function') } getPythonBaseMethods () { return this.getBaseMethods () } getPHPBaseMethods () { return this.getBaseMethods () } //------------------------------------------------------------------------- // the following common headers are used for transpiled tests getPythonPreamble () { return [ "import os", "import sys", "", "root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))", "sys.path.append(root)", "", "# ----------------------------------------------------------------------------", "", "# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:", "# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code", "", "# ----------------------------------------------------------------------------", "", ].join ("\n") } getPHPPreamble (include = true) { return [ "<?php", "namespace ccxt;", include ? "include_once (__DIR__.'/../../ccxt.php');" : "", "// ----------------------------------------------------------------------------", "", "// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:", "// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code", "", "// -----------------------------------------------------------------------------", "", ].join ("\n") } // ------------------------------------------------------------------------ // a helper to apply an array of regexes and substitutions to text // accepts an array like [ [ regex, substitution ], ... ] regexAll (text, array) { for (const i in array) { let regex = array[i][0] const flags = (typeof regex === 'string') ? 'g' : undefined regex = new RegExp (regex, flags) text = text.replace (regex, array[i][1]) } return text } // ======================================================================== // one-time helpers createPythonClassDeclaration (className, baseClass) { return 'class ' + className + '(' + baseClass + '):' } createPythonHeader () { return [ pythonCodingUtf8, "", "# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:", "# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code", "", ] } createPythonClassHeader (imports, bodyAsString) { const header = this.createPythonHeader () return header.concat (imports); } createPythonClassImports (baseClass, async = false) { const baseClasses = { 'Exchange': 'base.exchange', } async = (async ? '.async_support' : '') return [ (baseClass.indexOf ('ccxt.') === 0) ? ('import ccxt' + async + ' as ccxt') : ('from ccxt' + async + '.' + safeString (baseClasses, baseClass, baseClass) + ' import ' + baseClass) ] } createPythonClass (className, baseClass, body, methods, async = false) { let bodyAsString = body.join ("\n") const { imports, asyncioImports, libraries, errorImports, precisionImports } = this.createPythonImports(baseClass, bodyAsString, async) let header = this.createPythonClassHeader (imports, bodyAsString) header = header.concat (asyncioImports, libraries, errorImports, precisionImports) // transpile camelCase base method names to underscore base method names const baseMethods = this.getPythonBaseMethods () methods = methods.concat (baseMethods) for (let method of methods) { const regex = new RegExp ('(self|super\\([^)]+\\))\\.(' + method + ')([^a-zA-Z0-9_])', 'g') bodyAsString = bodyAsString.replace (regex, (match, p1, p2, p3) => (p1 + '.' + unCamelCase (p2) + p3)) } header.push ("\n\n" + this.createPythonClassDeclaration (className, baseClass)) const footer = [ '', // footer (last empty line) ] const result = header.join ("\n") + "\n" + bodyAsString + "\n" + footer.join ('\n') return result } createPythonImports (baseClass, bodyAsString, async = false) { async = (async ? '.async_support' : '') const pythonStandardLibraries = { 'hashlib': 'hashlib', 'math': 'math', 'json.loads': 'json', 'json.dumps': 'json', 'sys': 'sys', } const imports = this.createPythonClassImports (baseClass, async) const libraries = [] for (let library in pythonStandardLibraries) { const regex = new RegExp ("[^\\'\\\"a-zA-Z]" + library + "[^\\'\\\"a-zA-Z]") if (bodyAsString.match (regex)){ const importStatement = 'import ' + pythonStandardLibraries[library]; if (!libraries.includes(importStatement)) { libraries.push (importStatement) } } } if (bodyAsString.match (/numbers\.(Real|Integral)/)) { libraries.push ('import numbers') } const errorImports = [] for (let error in errors) { const regex = new RegExp ("[^\\'\"]" + error + "[^\\'\"]") if (bodyAsString.match (regex)) { errorImports.push ('from ccxt.base.errors import ' + error) } } const precisionImports = [] for (let constant in precisionConstants) { if (bodyAsString.indexOf (constant) >= 0) { precisionImports.push ('from ccxt.base.decimal_to_precision import ' + constant) } } if (bodyAsString.match (/[\s(]Precise/)) { precisionImports.push ('from ccxt.base.precise import Precise') } const asyncioImports = [] if (bodyAsString.match (/asyncio/)) { asyncioImports.push ('import asyncio') } return { imports, asyncioImports, libraries, errorImports, precisionImports } } // ======================================================================== // exchange capabilities ordering sortExchangeCapabilities (code) { const lineBreak = '\n'; const capabilitiesObjectRegex = /(?<='has': {[\n])([^|})]*)(?=\n(\s+}))/; const found = capabilitiesObjectRegex.exec (code); if (found === null) { return false // capabilities not found } let capabilities = found[0].split (lineBreak); const sortingOrder = { 'CORS': 'undefined,', 'spot': 'true,', 'margin': 'undefined,', 'swap': 'undefined,', 'future': 'undefined,', 'option': 'undefined,', // then everything else } const features = {} let indentation = ' ' // 16 spaces for (let i = 0; i < capabilities.length; i++) { const capability = capabilities[i] const match = capability.match (/(\s+)\'(.+)\': (.+)$/) if (match) { indentation = match[1] const feature = match[2] const value = match[3] features[feature] = value } } let keys = Object.keys (features) keys.sort ((a, b) => a.localeCompare (b)) const allKeys = Object.keys (sortingOrder).concat (keys) for (let i = 0; i < allKeys.length; i++) { const key = allKeys[i] sortingOrder[key] = (key in features) ? features[key] : sortingOrder[key] } const result = Object.entries (sortingOrder).map (([ key, value ]) => indentation + "'" + key + "': " + value).join (lineBreak) if (result === found[0]) { return false } return code.replace (capabilitiesObjectRegex, result) } // ------------------------------------------------------------------------ createPHPClassDeclaration (className, baseClass) { return 'class ' + className + ' extends ' + baseClass + ' {' } createPHPClassHeader (className, baseClass, bodyAsString, namespace) { return [ "<?php", "", "namespace " + namespace + ";", "", "// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:", "// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code", "", "use Exception; // a common import", ] } createPHPClass (className, baseClass, body, methods, async = false) { let bodyAsString = body.join ("\n") let header = this.createPHPClassHeader (className, baseClass, bodyAsString, async ? 'ccxt\\async' : 'ccxt') const errorImports = [] if (async) { for (let error in errors) { const regex = new RegExp ("[^'\"]" + error + "[^'\"]") if (bodyAsString.match (regex)) { errorImports.push ('use ccxt\\' + error + ';') } } } const precisionImports = [] const libraryImports = [] if (async) { if (bodyAsString.match (/[\s(]Precise/)) { precisionImports.push ('use ccxt\\Precise;') } if (bodyAsString.match (/Async\\await/)) { libraryImports.push ('use React\\Async;') } if (bodyAsString.match (/Promise\\all/)) { libraryImports.push ('use React\\Promise;') } } header = header.concat (errorImports).concat (precisionImports).concat (libraryImports) // transpile camelCase base method names to underscore base method names const baseMethods = this.getPHPBaseMethods () methods = methods.concat (baseMethods) for (let method of methods) { let regex = new RegExp ('\\$this->(' + method + ')\\s?(\\(|[^a-zA-Z0-9_])', 'g') bodyAsString = bodyAsString.replace (regex, (match, p1, p2) => { return ((p2 === '(') ? ('$this->' + unCamelCase (p1) + p2) : // support direct php calls ("array($this, '" + unCamelCase (p1) + "')" + p2)) // as well as passing instance methods as callables }) regex = new RegExp ('parent::(' + method + ')\\s?(\\(|[^a-zA-Z0-9_])', 'g') bodyAsString = bodyAsString.replace (regex, (match, p1, p2) => { return ((p2 === '(') ? ('parent::' + unCamelCase (p1) + p2) : // support direct php calls ("array($this, '" + unCamelCase (p1) + "')" + p2)) // as well as passing instance methods as callables }) } header.push ("\n" + this.createPHPClassDeclaration (className, baseClass)) const footer = [ "}\n", ] const result = header.join ("\n") + "\n" + bodyAsString + "\n" + footer.join ('\n') return result } // ======================================================================== transpileJavaScriptToPython3 ({ js, className, removeEmptyLines }) { // transpile JS → Python 3 let python3Body = this.regexAll (js, this.getPythonRegexes ()) if (removeEmptyLines) { python3Body = python3Body.replace (/$\s*$/gm, '') } const strippedPython3BodyWithoutComments = python3Body.replace (/^[\s]+#.+$/gm, '') if (!strippedPython3BodyWithoutComments.match(/[^\s]/)) {