ccxt-look 1.81.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/.cache/eslintcache +1 -0
  2. package/.dockerignore +6 -0
  3. package/.eslintignore +1 -0
  4. package/.gitattributes +5 -0
  5. package/.readthedocs.yaml +16 -0
  6. package/CONTRIBUTING.md +1049 -0
  7. package/LICENSE.txt +21 -0
  8. package/README.md +537 -0
  9. package/SECURITY.md +5 -0
  10. package/build/cleanup-old-tags.js +94 -0
  11. package/build/countries.js +256 -0
  12. package/build/export-exchanges.js +520 -0
  13. package/build/fs.js +51 -0
  14. package/build/transpile.js +1772 -0
  15. package/build/vss.js +78 -0
  16. package/ccxt.browser.js +7 -0
  17. package/ccxt.d.ts +692 -0
  18. package/ccxt.js +171 -0
  19. package/cleanup.sh +2 -0
  20. package/composer-install.sh +20 -0
  21. package/dist/ccxt.browser.js +208383 -0
  22. package/gource.sh +3 -0
  23. package/index.html +7 -0
  24. package/js/.eslintrc +87 -0
  25. package/js/aax.js +2686 -0
  26. package/js/ascendex.js +2584 -0
  27. package/js/base/.eslintrc.js +43 -0
  28. package/js/base/Exchange.js +2371 -0
  29. package/js/base/Precise.js +283 -0
  30. package/js/base/errorHierarchy.js +47 -0
  31. package/js/base/errors.js +55 -0
  32. package/js/base/functions/crypto.js +158 -0
  33. package/js/base/functions/encode.js +118 -0
  34. package/js/base/functions/generic.js +270 -0
  35. package/js/base/functions/misc.js +138 -0
  36. package/js/base/functions/number.js +329 -0
  37. package/js/base/functions/platform.js +38 -0
  38. package/js/base/functions/string.js +21 -0
  39. package/js/base/functions/throttle.js +79 -0
  40. package/js/base/functions/time.js +210 -0
  41. package/js/base/functions/type.js +66 -0
  42. package/js/base/functions.js +28 -0
  43. package/js/bequant.js +32 -0
  44. package/js/bibox.js +1407 -0
  45. package/js/bigone.js +1366 -0
  46. package/js/binance.js +5652 -0
  47. package/js/binancecoinm.js +46 -0
  48. package/js/binanceus.js +46 -0
  49. package/js/binanceusdm.js +49 -0
  50. package/js/bit2c.js +535 -0
  51. package/js/bitbank.js +842 -0
  52. package/js/bitbay.js +16 -0
  53. package/js/bitbns.js +1073 -0
  54. package/js/bitcoincom.js +15 -0
  55. package/js/bitfinex.js +1433 -0
  56. package/js/bitfinex2.js +2025 -0
  57. package/js/bitflyer.js +840 -0
  58. package/js/bitforex.js +614 -0
  59. package/js/bitget.js +2397 -0
  60. package/js/bithumb.js +980 -0
  61. package/js/bitmart.js +2516 -0
  62. package/js/bitmex.js +1809 -0
  63. package/js/bitopro.js +1443 -0
  64. package/js/bitpanda.js +1782 -0
  65. package/js/bitrue.js +1747 -0
  66. package/js/bitso.js +1062 -0
  67. package/js/bitstamp.js +1757 -0
  68. package/js/bitstamp1.js +343 -0
  69. package/js/bittrex.js +1876 -0
  70. package/js/bitvavo.js +1579 -0
  71. package/js/bkex.js +1233 -0
  72. package/js/bl3p.js +346 -0
  73. package/js/blockchaincom.js +969 -0
  74. package/js/btcalpha.js +680 -0
  75. package/js/btcbox.js +477 -0
  76. package/js/btcmarkets.js +1022 -0
  77. package/js/btctradeua.js +466 -0
  78. package/js/btcturk.js +734 -0
  79. package/js/buda.js +946 -0
  80. package/js/bw.js +1265 -0
  81. package/js/bybit.js +3372 -0
  82. package/js/bytetrade.js +1336 -0
  83. package/js/cdax.js +1646 -0
  84. package/js/cex.js +1410 -0
  85. package/js/coinbase.js +1342 -0
  86. package/js/coinbaseprime.js +31 -0
  87. package/js/coinbasepro.js +1466 -0
  88. package/js/coincheck.js +755 -0
  89. package/js/coinex.js +3400 -0
  90. package/js/coinfalcon.js +880 -0
  91. package/js/coinmate.js +794 -0
  92. package/js/coinone.js +816 -0
  93. package/js/coinspot.js +345 -0
  94. package/js/crex24.js +1636 -0
  95. package/js/cryptocom.js +1832 -0
  96. package/js/currencycom.js +1748 -0
  97. package/js/delta.js +1547 -0
  98. package/js/deribit.js +2148 -0
  99. package/js/digifinex.js +1585 -0
  100. package/js/eqonex.js +1660 -0
  101. package/js/exmo.js +1670 -0
  102. package/js/fairdesk.js +1231 -0
  103. package/js/flowbtc.js +35 -0
  104. package/js/fmfwio.js +34 -0
  105. package/js/ftx.js +2751 -0
  106. package/js/ftxus.js +38 -0
  107. package/js/gateio.js +4174 -0
  108. package/js/gemini.js +1397 -0
  109. package/js/hitbtc.js +1343 -0
  110. package/js/hitbtc3.js +2329 -0
  111. package/js/hollaex.js +1486 -0
  112. package/js/huobi.js +5706 -0
  113. package/js/huobijp.js +1710 -0
  114. package/js/huobipro.js +18 -0
  115. package/js/idex.js +1439 -0
  116. package/js/independentreserve.js +649 -0
  117. package/js/indodax.js +742 -0
  118. package/js/itbit.js +722 -0
  119. package/js/kraken.js +2179 -0
  120. package/js/kucoin.js +2571 -0
  121. package/js/kucoinfutures.js +1771 -0
  122. package/js/kuna.js +809 -0
  123. package/js/latoken.js +1445 -0
  124. package/js/lbank.js +760 -0
  125. package/js/liquid.js +1432 -0
  126. package/js/luno.js +873 -0
  127. package/js/lykke.js +1147 -0
  128. package/js/mercado.js +771 -0
  129. package/js/mexc.js +3151 -0
  130. package/js/ndax.js +2233 -0
  131. package/js/novadax.js +1318 -0
  132. package/js/oceanex.js +816 -0
  133. package/js/okcoin.js +3841 -0
  134. package/js/okex.js +16 -0
  135. package/js/okex5.js +16 -0
  136. package/js/okx.js +4795 -0
  137. package/js/paymium.js +498 -0
  138. package/js/phemex.js +2957 -0
  139. package/js/poloniex.js +1674 -0
  140. package/js/probit.js +1346 -0
  141. package/js/qtrade.js +1588 -0
  142. package/js/ripio.js +1061 -0
  143. package/js/static_dependencies/BN/bn.js +3526 -0
  144. package/js/static_dependencies/README.md +1 -0
  145. package/js/static_dependencies/crypto-js/crypto-js.js +5988 -0
  146. package/js/static_dependencies/elliptic/lib/elliptic/curve/base.js +375 -0
  147. package/js/static_dependencies/elliptic/lib/elliptic/curve/edwards.js +433 -0
  148. package/js/static_dependencies/elliptic/lib/elliptic/curve/index.js +8 -0
  149. package/js/static_dependencies/elliptic/lib/elliptic/curve/mont.js +180 -0
  150. package/js/static_dependencies/elliptic/lib/elliptic/curve/short.js +938 -0
  151. package/js/static_dependencies/elliptic/lib/elliptic/curves.js +204 -0
  152. package/js/static_dependencies/elliptic/lib/elliptic/ec/index.js +240 -0
  153. package/js/static_dependencies/elliptic/lib/elliptic/ec/key.js +119 -0
  154. package/js/static_dependencies/elliptic/lib/elliptic/ec/signature.js +24 -0
  155. package/js/static_dependencies/elliptic/lib/elliptic/eddsa/index.js +145 -0
  156. package/js/static_dependencies/elliptic/lib/elliptic/eddsa/key.js +100 -0
  157. package/js/static_dependencies/elliptic/lib/elliptic/eddsa/signature.js +65 -0
  158. package/js/static_dependencies/elliptic/lib/elliptic/precomputed/secp256k1.js +780 -0
  159. package/js/static_dependencies/elliptic/lib/elliptic/utils.js +214 -0
  160. package/js/static_dependencies/elliptic/lib/elliptic.js +22 -0
  161. package/js/static_dependencies/elliptic/lib/hmac-drbg/hmac-drbg.js +114 -0
  162. package/js/static_dependencies/fetch-ponyfill/fetch-node.js +39 -0
  163. package/js/static_dependencies/node-fetch/index.js +1564 -0
  164. package/js/static_dependencies/node-rsa/NodeRSA.js +223 -0
  165. package/js/static_dependencies/node-rsa/asn1/ber/errors.js +13 -0
  166. package/js/static_dependencies/node-rsa/asn1/ber/index.js +21 -0
  167. package/js/static_dependencies/node-rsa/asn1/ber/reader.js +262 -0
  168. package/js/static_dependencies/node-rsa/asn1/ber/types.js +36 -0
  169. package/js/static_dependencies/node-rsa/asn1/index.js +17 -0
  170. package/js/static_dependencies/node-rsa/encryptEngines/js.js +34 -0
  171. package/js/static_dependencies/node-rsa/formats/components.js +71 -0
  172. package/js/static_dependencies/node-rsa/formats/formats.js +31 -0
  173. package/js/static_dependencies/node-rsa/formats/pkcs1.js +148 -0
  174. package/js/static_dependencies/node-rsa/formats/pkcs8.js +187 -0
  175. package/js/static_dependencies/node-rsa/libs/jsbn.js +1252 -0
  176. package/js/static_dependencies/node-rsa/libs/rsa.js +147 -0
  177. package/js/static_dependencies/node-rsa/schemes/pkcs1.js +176 -0
  178. package/js/static_dependencies/node-rsa/schemes/schemes.js +21 -0
  179. package/js/static_dependencies/node-rsa/utils.js +98 -0
  180. package/js/static_dependencies/qs/formats.js +18 -0
  181. package/js/static_dependencies/qs/index.js +11 -0
  182. package/js/static_dependencies/qs/parse.js +242 -0
  183. package/js/static_dependencies/qs/stringify.js +269 -0
  184. package/js/static_dependencies/qs/utils.js +230 -0
  185. package/js/stex.js +1925 -0
  186. package/js/test/.eslintrc.js +42 -0
  187. package/js/test/Exchange/test.balance.js +61 -0
  188. package/js/test/Exchange/test.borrowRate.js +32 -0
  189. package/js/test/Exchange/test.currency.js +52 -0
  190. package/js/test/Exchange/test.fetchBalance.js +23 -0
  191. package/js/test/Exchange/test.fetchBorrowInterest.js +59 -0
  192. package/js/test/Exchange/test.fetchBorrowRate.js +32 -0
  193. package/js/test/Exchange/test.fetchBorrowRates.js +28 -0
  194. package/js/test/Exchange/test.fetchClosedOrders.js +32 -0
  195. package/js/test/Exchange/test.fetchCurrencies.js +35 -0
  196. package/js/test/Exchange/test.fetchDeposits.js +31 -0
  197. package/js/test/Exchange/test.fetchFundingFees.js +19 -0
  198. package/js/test/Exchange/test.fetchFundingRateHistory.js +40 -0
  199. package/js/test/Exchange/test.fetchL2OrderBook.js +23 -0
  200. package/js/test/Exchange/test.fetchLedger.js +42 -0
  201. package/js/test/Exchange/test.fetchLeverageTiers.js +33 -0
  202. package/js/test/Exchange/test.fetchMarketLeverageTiers.js +22 -0
  203. package/js/test/Exchange/test.fetchMarkets.js +33 -0
  204. package/js/test/Exchange/test.fetchMyTrades.js +42 -0
  205. package/js/test/Exchange/test.fetchOHLCV.js +46 -0
  206. package/js/test/Exchange/test.fetchOpenOrders.js +36 -0
  207. package/js/test/Exchange/test.fetchOrderBook.js +25 -0
  208. package/js/test/Exchange/test.fetchOrderBooks.js +35 -0
  209. package/js/test/Exchange/test.fetchOrders.js +41 -0
  210. package/js/test/Exchange/test.fetchPositions.js +47 -0
  211. package/js/test/Exchange/test.fetchStatus.js +35 -0
  212. package/js/test/Exchange/test.fetchTicker.js +38 -0
  213. package/js/test/Exchange/test.fetchTickers.js +49 -0
  214. package/js/test/Exchange/test.fetchTrades.js +39 -0
  215. package/js/test/Exchange/test.fetchTradingFee.js +18 -0
  216. package/js/test/Exchange/test.fetchTradingFees.js +22 -0
  217. package/js/test/Exchange/test.fetchTransactions.js +31 -0
  218. package/js/test/Exchange/test.fetchWithdrawals.js +31 -0
  219. package/js/test/Exchange/test.ledgerItem.js +46 -0
  220. package/js/test/Exchange/test.leverageTier.js +33 -0
  221. package/js/test/Exchange/test.loadMarkets.js +35 -0
  222. package/js/test/Exchange/test.market.js +129 -0
  223. package/js/test/Exchange/test.ohlcv.js +33 -0
  224. package/js/test/Exchange/test.order.js +62 -0
  225. package/js/test/Exchange/test.orderbook.js +61 -0
  226. package/js/test/Exchange/test.position.js +21 -0
  227. package/js/test/Exchange/test.throttle.js +94 -0
  228. package/js/test/Exchange/test.ticker.js +95 -0
  229. package/js/test/Exchange/test.trade.js +68 -0
  230. package/js/test/Exchange/test.tradingFee.js +34 -0
  231. package/js/test/Exchange/test.transaction.js +35 -0
  232. package/js/test/base/.eslintrc +38 -0
  233. package/js/test/base/functions/test.crypto.js +110 -0
  234. package/js/test/base/functions/test.datetime.js +62 -0
  235. package/js/test/base/functions/test.generic.js +152 -0
  236. package/js/test/base/functions/test.number.js +362 -0
  237. package/js/test/base/functions/test.time.js +56 -0
  238. package/js/test/base/functions/test.type.js +53 -0
  239. package/js/test/base/test.base.js +193 -0
  240. package/js/test/errors/test.InsufficientFunds.js +86 -0
  241. package/js/test/errors/test.InvalidNonce.js +64 -0
  242. package/js/test/errors/test.InvalidOrder.js +35 -0
  243. package/js/test/errors/test.OrderNotFound.js +39 -0
  244. package/js/test/test.js +426 -0
  245. package/js/test/test.timeout_hang.js +12 -0
  246. package/js/therock.js +1431 -0
  247. package/js/tidebit.js +632 -0
  248. package/js/tidex.js +939 -0
  249. package/js/timex.js +1283 -0
  250. package/js/upbit.js +1622 -0
  251. package/js/vcc.js +1353 -0
  252. package/js/wavesexchange.js +2185 -0
  253. package/js/wazirx.js +732 -0
  254. package/js/whitebit.js +1352 -0
  255. package/js/woo.js +1577 -0
  256. package/js/xena.js +1948 -0
  257. package/js/yobit.js +1129 -0
  258. package/js/zaif.js +647 -0
  259. package/js/zb.js +4088 -0
  260. package/js/zipmex.js +40 -0
  261. package/js/zonda.js +1497 -0
  262. package/multilang.sh +159 -0
  263. package/package.json +591 -0
  264. package/postinstall.js +103 -0
@@ -0,0 +1,1772 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Usage: npm run transpile
3
+ // ---------------------------------------------------------------------------
4
+
5
+ "use strict";
6
+
7
+ const fs = require ('fs')
8
+ , log = require ('ololog').unlimited
9
+ , _ = require ('ansicolor').nice
10
+ , errors = require ('../js/base/errors.js')
11
+ , functions = require ('../js/base/functions.js')
12
+ , {
13
+ unCamelCase,
14
+ precisionConstants,
15
+ safeString,
16
+ unique,
17
+ } = functions
18
+ , { basename } = require ('path')
19
+ , {
20
+ createFolderRecursively,
21
+ replaceInFile,
22
+ overwriteFile,
23
+ } = require ('./fs.js')
24
+ , Exchange = require ('../js/base/Exchange.js')
25
+ , tsFilename = './ccxt.d.ts'
26
+
27
+ class Transpiler {
28
+
29
+ getCommonRegexes () {
30
+ return [
31
+
32
+ [ /\.deepExtend\s/g, '.deep_extend'],
33
+ [ /\.safeFloat2\s/g, '.safe_float_2'],
34
+ [ /\.safeInteger2\s/g, '.safe_integer_2'],
35
+ [ /\.safeIntegerProduct2\s/g, '.safe_integer_product_2'],
36
+ [ /\.safeTimestamp2\s/g, '.safe_timestamp_2'],
37
+ [ /\.safeString2\s/g, '.safe_string_2'],
38
+ [ /\.safeNumber2\s/g, '.safe_number_2'],
39
+ [ /\.safeStringLower2\s/g, '.safe_string_lower_2'],
40
+ [ /\.safeStringUpper2\s/g, '.safe_string_upper_2'],
41
+ [ /\.safeValue2\s/g, '.safe_value_2'],
42
+ [ /\.safeFloat\s/g, '.safe_float'],
43
+ [ /\.safeInteger\s/g, '.safe_integer'],
44
+ [ /\.safeIntegerProduct\s/g, '.safe_integer_product'],
45
+ [ /\.safeTimestamp\s/g, '.safe_timestamp'],
46
+ [ /\.safeString\s/g, '.safe_string'],
47
+ [ /\.safeStringLower\s/g, '.safe_string_lower'],
48
+ [ /\.safeStringUpper\s/g, '.safe_string_upper'],
49
+ [ /\.safeValue\s/g, '.safe_value'],
50
+ [ /\.inArray\s/g, '.in_array'],
51
+ [ /\.toArray\s/g, '.to_array'],
52
+ [ /\.isEmpty\s/g, '.is_empty'],
53
+ [ /\.arrayConcat\s/g, '.array_concat'],
54
+ [ /\.binaryConcat\s/g, '.binary_concat'],
55
+ [ /\.binaryConcatArray\s/g, '.binary_concat_array'],
56
+ [ /\.binaryToString\s/g, '.binary_to_string' ],
57
+ [ /\.precisionFromString\s/g, '.precision_from_string'],
58
+ [ /\.parsePrecision\s/g, '.parse_precision'],
59
+ [ /\.implodeHostname\s/g, '.implode_hostname'],
60
+ [ /\.implodeParams\s/g, '.implode_params'],
61
+ [ /\.extractParams\s/g, '.extract_params'],
62
+ [ /\.safeBalance\s/g, '.safe_balance'],
63
+ [ /\.parseBalance\s/g, '.parse_balance'],
64
+ [ /\.parseOHLCVs\s/g, '.parse_ohlcvs'],
65
+ [ /\.parseOHLCV\s/g, '.parse_ohlcv'],
66
+ [ /\.parseDate\s/g, '.parse_date'],
67
+ [ /\.parseLedgerEntry\s/g, '.parse_ledger_entry'],
68
+ [ /\.parseLedger\s/g, '.parse_ledger'],
69
+ [ /\.parseTickers\s/g, '.parse_tickers'],
70
+ [ /\.parseTicker\s/g, '.parse_ticker'],
71
+ [ /\.parseTimeframe\s/g, '.parse_timeframe'],
72
+ [ /\.parseTradesData\s/g, '.parse_trades_data'],
73
+ [ /\.parseTrades\s/g, '.parse_trades'],
74
+ [ /\.parseTrade\s/g, '.parse_trade'],
75
+ [ /\.parseTradingViewOHLCV\s/g, '.parse_trading_view_ohlcv'],
76
+ [ /\.convertTradingViewToOHLCV\s/g, '.convert_trading_view_to_ohlcv'],
77
+ [ /\.parseTransaction\s/g, '.parse_transaction'],
78
+ [ /\.parseTransactions\s/g, '.parse_transactions'],
79
+ [ /\.parseOrderBook\s/g, '.parse_order_book'],
80
+ [ /\.parseBidsAsks\s/g, '.parse_bids_asks'],
81
+ [ /\.parseBidAsk\s/g, '.parse_bid_ask'],
82
+ [ /\.parseOrders\s/g, '.parse_orders'],
83
+ [ /\.parseOrderStatus\s/g, '.parse_order_status'],
84
+ [ /\.parseOrder\s/g, '.parse_order'],
85
+ [ /\.parseJson\s/g, '.parse_json'],
86
+ [ /\.filterByArray\s/g, '.filter_by_array'],
87
+ [ /\.filterBySymbolSinceLimit\s/g, '.filter_by_symbol_since_limit'],
88
+ [ /\.filterBySinceLimit\s/g, '.filter_by_since_limit'],
89
+ [ /\.filterBySymbol\s/g, '.filter_by_symbol'],
90
+ [ /\.getVersionString\s/g, '.get_version_string'],
91
+ [ /\.indexBy\s/g, '.index_by'],
92
+ [ /\.sortBy\s/g, '.sort_by'],
93
+ [ /\.sortBy2\s/g, '.sort_by_2'],
94
+ [ /\.filterBy\s/g, '.filter_by'],
95
+ [ /\.groupBy\s/g, '.group_by'],
96
+ [ /\.marketIds\s/g, '.market_ids'],
97
+ [ /\.marketId\s/g, '.market_id'],
98
+ [ /\.fetchFundingFees\s/g, '.fetch_funding_fees'],
99
+ [ /\.fetchTradingFees\s/g, '.fetch_trading_fees'],
100
+ [ /\.fetchTradingFee\s/g, '.fetch_trading_fee'],
101
+ [ /\.fetchFees\s/g, '.fetch_fees'],
102
+ [ /\.fetchL2OrderBook\s/g, '.fetch_l2_order_book'],
103
+ [ /\.fetchOrderBook\s/g, '.fetch_order_book'],
104
+ [ /\.fetchMyTrades\s/g, '.fetch_my_trades'],
105
+ [ /\.fetchOrderStatus\s/g, '.fetch_order_status'],
106
+ [ /\.fetchOpenOrders\s/g, '.fetch_open_orders'],
107
+ [ /\.fetchOpenOrder\s/g, '.fetch_open_order'],
108
+ [ /\.fetchOrders\s/g, '.fetch_orders'],
109
+ [ /\.fetchOrderTrades\s/g, '.fetch_order_trades'],
110
+ [ /\.fetchOrder\s/g, '.fetch_order'],
111
+ [ /\.fetchBidsAsks\s/g, '.fetch_bids_asks'],
112
+ [ /\.fetchTickers\s/g, '.fetch_tickers'],
113
+ [ /\.fetchTicker\s/g, '.fetch_ticker'],
114
+ [ /\.fetchCurrencies\s/g, '.fetch_currencies'],
115
+ [ /\.numberToString\s/g, '.number_to_string' ],
116
+ [ /\.decimalToPrecision\s/g, '.decimal_to_precision'],
117
+ [ /\.priceToPrecision\s/g, '.price_to_precision'],
118
+ [ /\.amountToPrecision\s/g, '.amount_to_precision'],
119
+ [ /\.amountToLots\s/g, '.amount_to_lots'],
120
+ [ /\.feeToPrecision\s/g, '.fee_to_precision'],
121
+ [ /\.currencyToPrecision\s/g, '.currency_to_precision'],
122
+ [ /\.costToPrecision\s/g, '.cost_to_precision'],
123
+ [ /\.commonCurrencyCode\s/g, '.common_currency_code'],
124
+ [ /\.loadAccounts\s/g, '.load_accounts'],
125
+ [ /\.loadFees\s/g, '.load_fees'],
126
+ [ /\.loadMarkets\s/g, '.load_markets'],
127
+ [ /\.loadTimeDifference\s/g, '.load_time_difference'],
128
+ [ /\.fetchMarkets\s/g, '.fetch_markets'],
129
+ [ /\.appendInactiveMarkets\s/g, '.append_inactive_markets'],
130
+ [ /\.fetchCategories\s/g, '.fetch_categories'],
131
+ [ /\.calculateFee\s/g, '.calculate_fee'],
132
+ [ /\.createOrder\s/g, '.create_order'],
133
+ [ /\.createPostOnlyOrder\s/g, '.create_post_only_order'],
134
+ [ /\.createStopOrder\s/g, '.create_stop_order'],
135
+ [ /\.createStopLimitOrder\s/g, '.create_stop_limit_order'],
136
+ [ /\.createStopMarketOrder\s/g, '.create_stop_market_order'],
137
+ [ /\.editLimitBuyOrder\s/g, '.edit_limit_buy_order'],
138
+ [ /\.editLimitSellOrder\s/g, '.edit_limit_sell_order'],
139
+ [ /\.editLimitOrder\s/g, '.edit_limit_order'],
140
+ [ /\.editOrder\s/g, '.edit_order'],
141
+ [ /\.encodeURIComponent\s/g, '.encode_uri_component'],
142
+ [ /\.throwExceptionOnError\s/g, '.throw_exception_on_error'],
143
+ [ /\.handleErrors\s/g, '.handle_errors'],
144
+ [ /\.checkRequiredCredentials\s/g, '.check_required_credentials'],
145
+ [ /\.checkRequiredDependencies\s/g, '.check_required_dependencies'],
146
+ [ /\.checkAddress\s/g, '.check_address'],
147
+ [ /\.convertTradingViewToOHLCV\s/g, '.convert_trading_view_to_ohlcv'],
148
+ [ /\.convertOHLCVToTradingView\s/g, '.convert_ohlcv_to_trading_view'],
149
+ [ /\.signBodyWithSecret\s/g, '.sign_body_with_secret'],
150
+ [ /\.isJsonEncodedObject\s/g, '.is_json_encoded_object'],
151
+ [ /\.setSandboxMode\s/g, '.set_sandbox_mode'],
152
+ [ /\.safeCurrencyCode\s/g, '.safe_currency_code'],
153
+ [ /\.safeCurrency\s/g, '.safe_currency'],
154
+ [ /\.safeSymbol\s/g, '.safe_symbol'],
155
+ [ /\.safeMarket\s/g, '.safe_market'],
156
+ [ /\.safeOrder\s/g, '.safe_order'],
157
+ [ /\.safeTicker\s/g, '.safe_ticker'],
158
+ [ /\.roundTimeframe\s/g, '.round_timeframe'],
159
+ [ /\.calculateRateLimiterCost\s/g, '.calculate_rate_limiter_cost' ],
160
+ [ /\.parseAccountPosition\s/g, '.parse_account_position' ],
161
+ [ /\.parsePositionRisk\s/g, '.parse_position_risk' ],
162
+ [ /\.parseIncome\s/g, '.parse_income' ],
163
+ [ /\.parseIncomes\s/g, '.parse_incomes' ],
164
+ [ /\.parseFundingRate\s/g, '.parse_funding_rate' ],
165
+ [ /\.findBroadlyMatchedKey\s/g, '.find_broadly_matched_key' ],
166
+ [ /\.throwBroadlyMatchedException\s/g, '.throw_broadly_matched_exception' ],
167
+ [ /\.throwExactlyMatchedException\s/g, '.throw_exactly_matched_exception' ],
168
+ [ /\.getNetwork\s/g, '.get_network' ],
169
+ [ /\.findTimeframe\s/g, '.find_timeframe'],
170
+ [ /errorHierarchy/g, 'error_hierarchy'],
171
+ [ /\.base16ToBinary/g, '.base16_to_binary'],
172
+ [ /\'use strict\';?\s+/g, '' ],
173
+ [ /\.urlencodeNested\s/g, '.urlencode_nested' ],
174
+ [ /\.urlencodeWithArrayRepeat\s/g, '.urlencode_with_array_repeat' ],
175
+ [ /\.call\s*\(this, /g, '(' ],
176
+ [ /\.getSupportedMapping\s/g, '.get_supported_mapping'],
177
+ [ /\.fetchBorrowRate\s/g, '.fetch_borrow_rate'],
178
+ [ /\.handleMarketTypeAndParams\s/g, '.handle_market_type_and_params'],
179
+ [ /\.isPostOnly\s/g, '.is_post_only'],
180
+ ]
181
+ }
182
+
183
+ getPythonRegexes () {
184
+
185
+ return [
186
+ [ /Array\.isArray\s*\(([^\)]+)\)/g, 'isinstance($1, list)' ],
187
+ [ /([^\(\s]+)\s+instanceof\s+String/g, 'isinstance($1, str)' ],
188
+ [ /([^\(\s]+)\s+instanceof\s+([^\)\s]+)/g, 'isinstance($1, $2)' ],
189
+
190
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'undefined\'/g, '$1[$2] is None' ],
191
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'undefined\'/g, '$1[$2] is not None' ],
192
+ [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'undefined\'/g, '$1 is None' ],
193
+ [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'undefined\'/g, '$1 is not None' ],
194
+ [ /typeof\s+(.+?)\s+\=\=\=?\s+\'undefined\'/g, '$1 is None' ],
195
+ [ /typeof\s+(.+?)\s+\!\=\=?\s+\'undefined\'/g, '$1 is not None' ],
196
+
197
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'number\'/g, "isinstance($1[$2], numbers.Real)" ],
198
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'number\'/g, "(not isinstance($1[$2], numbers.Real))" ],
199
+ [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'number\'/g, "isinstance($1, numbers.Real)" ],
200
+ [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'number\'/g, "(not isinstance($1, numbers.Real))" ],
201
+
202
+ [ /([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+undefined/g, '$1[$2] is None' ],
203
+ [ /([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+undefined/g, '$1[$2] is not None' ],
204
+ [ /([^\s]+)\s+\=\=\=?\s+undefined/g, '$1 is None' ],
205
+ [ /([^\s]+)\s+\!\=\=?\s+undefined/g, '$1 is not None' ],
206
+ [ /(.+?)\s+\=\=\=?\s+undefined/g, '$1 is None' ],
207
+ [ /(.+?)\s+\!\=\=?\s+undefined/g, '$1 is not None' ],
208
+ //
209
+ // too broad, have to rewrite these cause they don't work
210
+ //
211
+ // [ /([^\s]+)\s+\=\=\=?\s+true/g, 'isinstance($1, bool) and ($1 is True)' ],
212
+ // [ /([^\s]+)\s+\!\=\=?\s+true/g, 'isinstance($1, bool) and ($1 is not True)' ],
213
+ // [ /([^\s]+)\s+\=\=\=?\s+false/g, 'isinstance($1, bool) and ($1 is False)' ],
214
+ // [ /([^\s]+)\s+\!\=\=?\s+false/g, 'isinstance($1, bool) and ($1 is not False)' ],
215
+
216
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'string\'/g, 'isinstance($1[$2], str)' ],
217
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'string\'/g, 'not isinstance($1[$2], str)' ],
218
+ [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'string\'/g, 'isinstance($1, str)' ],
219
+ [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'string\'/g, 'not isinstance($1, str)' ],
220
+
221
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'object\'/g, 'isinstance($1[$2], dict)' ],
222
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'object\'/g, 'not isinstance($1[$2], dict)' ],
223
+ [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'object\'/g, 'isinstance($1, dict)' ],
224
+ [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'object\'/g, 'not isinstance($1, dict)' ],
225
+
226
+ [ /undefined/g, 'None' ],
227
+ [ /\=\=\=?/g, '==' ],
228
+ [ /\!\=\=?/g, '!=' ],
229
+ [ /this\.stringToBinary\s*\((.*)\)/g, '$1' ],
230
+ [ /\.shift\s*\(\)/g, '.pop(0)' ],
231
+ [ /Number\.MAX_SAFE_INTEGER/g, 'float(\'inf\')'],
232
+ [ /function\s*(\w+\s*\([^)]+\))\s*{/g, 'def $1:'],
233
+ // [ /\.replaceAll\s*\(([^)]+)\)/g, '.replace($1)' ], // still not a part of the standard
234
+ [ /assert\s*\((.+)\);/g, 'assert $1'],
235
+ [ /Promise\.all\s*\(([^\)]+)\)/g, 'asyncio.gather(*$1)' ],
236
+ [ /Precise\.stringAdd\s/g, 'Precise.string_add' ],
237
+ [ /Precise\.stringMul\s/g, 'Precise.string_mul' ],
238
+ [ /Precise\.stringDiv\s/g, 'Precise.string_div' ],
239
+ [ /Precise\.stringSub\s/g, 'Precise.string_sub' ],
240
+ [ /Precise\.stringAbs\s/g, 'Precise.string_abs' ],
241
+ [ /Precise\.stringNeg\s/g, 'Precise.string_neg' ],
242
+ [ /Precise\.stringMod\s/g, 'Precise.string_mod' ],
243
+ [ /Precise\.stringEquals\s/g, 'Precise.string_equals' ],
244
+ [ /Precise\.stringEq\s/g, 'Precise.string_eq' ],
245
+ [ /Precise\.stringMin\s/g, 'Precise.string_min' ],
246
+ [ /Precise\.stringMax\s/g, 'Precise.string_max' ],
247
+ [ /Precise\.stringGt\s/g, 'Precise.string_gt' ],
248
+ [ /Precise\.stringGe\s/g, 'Precise.string_ge' ],
249
+ [ /Precise\.stringLt\s/g, 'Precise.string_lt' ],
250
+ [ /Precise\.stringLe\s/g, 'Precise.string_le' ],
251
+
252
+ // insert common regexes in the middle (critical)
253
+ ].concat (this.getCommonRegexes ()).concat ([
254
+
255
+ // [ /this\.urlencode\s/g, '_urlencode.urlencode ' ], // use self.urlencode instead
256
+ [ /this\./g, 'self.' ],
257
+ [ /([^a-zA-Z\'])this([^a-zA-Z])/g, '$1self$2' ],
258
+ [ /\[\s*([^\]]+)\s\]\s=/g, '$1 =' ],
259
+ [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s\[\s*([^\]]+)\s\]/g, '$1$2' ],
260
+ [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s\{\s*([^\}]+)\s\}\s\=\s([^\;]+)/g, '$1$2 = (lambda $2: ($2))(**$3)' ],
261
+ [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s/g, '$1' ],
262
+ [ /Object\.keys\s*\((.*)\)\.length/g, '$1' ],
263
+ [ /Object\.keys\s*\((.*)\)/g, 'list($1.keys())' ],
264
+ [ /Object\.values\s*\((.*)\)/g, 'list($1.values())' ],
265
+ [ /\[([^\]]+)\]\.join\s*\(([^\)]+)\)/g, "$2.join([$1])" ],
266
+ [ /hash \(([^,]+)\, \'(sha[0-9])\'/g, "hash($1, '$2'" ],
267
+ [ /hmac \(([^,]+)\, ([^,]+)\, \'(md5)\'/g, 'hmac($1, $2, hashlib.$3' ],
268
+ [ /hmac \(([^,]+)\, ([^,]+)\, \'(sha[0-9]+)\'/g, 'hmac($1, $2, hashlib.$3' ],
269
+ [ /throw new ([\S]+) \((.*)\)/g, 'raise $1($2)'],
270
+ [ /throw ([\S]+)/g, 'raise $1'],
271
+ [ /try {/g, 'try:'],
272
+ [ /\}\s+catch \(([\S]+)\) {/g, 'except Exception as $1:'],
273
+ [ /([\s\(])extend(\s)/g, '$1self.extend$2' ],
274
+ [ /\} else if/g, 'elif' ],
275
+ [ /else if/g, 'elif' ],
276
+ [ /if\s+\((.*)\)\s+\{/g, 'if $1:' ],
277
+ [ /if\s+\((.*)\)\s*[\n]/g, "if $1:\n" ],
278
+ [ /\}\s*else\s*\{/g, 'else:' ],
279
+ [ /else\s*[\n]/g, "else:\n" ],
280
+ [ /for\s+\(([a-zA-Z0-9_]+)\s*=\s*([^\;\s]+\s*)\;[^\<\>\=]+(?:\<=|\>=|<|>)\s*(.*)\.length\s*\;[^\)]+\)\s*{/g, 'for $1 in range($2, len($3)):'],
281
+ [ /for\s+\(([a-zA-Z0-9_]+)\s*=\s*([^\;\s]+\s*)\;[^\<\>\=]+(?:\<=|\>=|<|>)\s*(.*)\s*\;[^\)]+\)\s*{/g, 'for $1 in range($2, $3):'],
282
+ [ /\s\|\|\s/g, ' or ' ],
283
+ [ /\s\&\&\s/g, ' and ' ],
284
+ [ /\!([^\s\='"])/g, 'not $1'],
285
+ [ /\.push\s*\(([\s\S]+?)\);/g, '.append($1);' ],
286
+ [ /^(\s*}\s*$)+/gm, '' ],
287
+ [ /\;(\s+?\/\/.+?)/g, '$1' ],
288
+ [ /\;$/gm, '' ],
289
+ [ /\.toUpperCase\s*/g, '.upper' ],
290
+ [ /\.toLowerCase\s*/g, '.lower' ],
291
+ [ /JSON\.stringify\s*/g, 'json.dumps' ],
292
+ [ /JSON\.parse\s*/g, "json.loads" ],
293
+ // [ /([^\(\s]+)\.includes\s+\(([^\)]+)\)/g, '$2 in $1' ],
294
+ // [ /\'%([^\']+)\'\.sprintf\s*\(([^\)]+)\)/g, "'{:$1}'.format($2)" ],
295
+ [ /([^\s]+)\.toFixed\s*\(([0-9]+)\)/g, "'{:.$2f}'.format($1)" ],
296
+ [ /([^\s]+)\.toFixed\s*\(([^\)]+)\)/g, "('{:.' + str($2) + 'f}').format($1)" ],
297
+ [ /parseFloat\s*/g, 'float'],
298
+ [ /parseInt\s*/g, 'int'],
299
+ [ /self\[([^\]+]+)\]/g, 'getattr(self, $1)' ],
300
+ [ /Math\.floor\s*\(([^\)]+)\)/g, 'int(math.floor($1))' ],
301
+ [ /Math\.abs\s*\(([^\)]+)\)/g, 'abs($1)' ],
302
+ [ /Math\.pow\s*\(([^\)]+)\)/g, 'math.pow($1)' ],
303
+ [ /Math\.round\s*\(([^\)]+)\)/g, 'int(round($1))' ],
304
+ [ /Math\.ceil\s*\(([^\)]+)\)/g, 'int(math.ceil($1))' ],
305
+ [ /Math\.log/g, 'math.log' ],
306
+ [ /([a-zA-Z0-9_\.]*\([^\)]+\)|[^\s]+)\s+\?\s*([^\:]+)\s+\:\s*([^\n]+)/g, '$2 if $1 else $3'],
307
+ [ /([^\s]+)\.slice \(([^\,\)]+)\,\s?([^\)]+)\)/g, '$1[$2:$3]' ],
308
+ [ /([^\s]+)\.slice \(([^\)\:]+)\)/g, '$1[$2:]' ],
309
+ [ /([^\s(:]+)\.length/g, 'len($1)' ],
310
+ [ /(^|\s)\/\//g, '$1#' ],
311
+ [ /([^\n\s]) #/g, '$1 #' ], // PEP8 E261
312
+ [ /\.indexOf/g, '.find'],
313
+ [ /(\s|\()true/g, '$1True'],
314
+ [ /(\s|\()false/g, '$1False'],
315
+ [ /([^\s]+\s*\(\))\.toString\s+\(\)/g, 'str($1)' ],
316
+ [ /([^\s]+)\.toString \(\)/g, 'str($1)' ],
317
+ [ /([^\s]+)\.join\s*\(\s*([^\)\[\]]+?)\s*\)/g, '$2.join($1)' ],
318
+ [ /Math\.(max|min)\s/g, '$1' ],
319
+ [ / = new /g, ' = ' ], // python does not have a 'new' keyword
320
+ [ /console\.log\s/g, 'print' ],
321
+ [ /process\.exit\s+/g, 'sys.exit' ],
322
+ [ /(while \(.*\)) {/, '$1\:' ], // While loops replace bracket with :
323
+ [ /([^:+=\/\*\s-]+) \(/g, '$1(' ], // PEP8 E225 remove whitespaces before left ( round bracket
324
+ [ /\sand\(/g, ' and (' ],
325
+ [ /\sor\(/g, ' or (' ],
326
+ [ /\snot\(/g, ' not (' ],
327
+ [ /\[ /g, '[' ], // PEP8 E201 remove whitespaces after left [ square bracket
328
+ [ /\{ /g, '{' ], // PEP8 E201 remove whitespaces after left { bracket
329
+ [ /(?<=[^\s#]) \]/g, ']' ], // PEP8 E202 remove whitespaces before right ] square bracket
330
+ [ /(?<=[^\s#]) \}/g, '}' ], // PEP8 E202 remove whitespaces before right } bracket
331
+ [ /([^a-z])(elif|if|or|else)\(/g, '$1$2 \(' ], // a correction for PEP8 E225 side-effect for compound and ternary conditionals
332
+ [ /\!\=\sTrue/g, 'is not True' ], // a correction for PEP8 E712, it likes "is not True", not "!= True"
333
+ [ /\=\=\sTrue/g, 'is True' ], // a correction for PEP8 E712, it likes "is True", not "== True"
334
+ [ /\sdelete\s/g, ' del ' ],
335
+ [ /(?<!#.+)null/, 'None' ],
336
+ [ /\/\*\*/, '\"\"\"' ], // Doc strings
337
+ [ / \*\//, '\"\"\"' ], // Doc strings
338
+ [ /\[(.*)\]\{@link (.*)\}/g, '`$1 <$2>`' ], // docstring item with link
339
+ [ /\s+\* @method/g, '' ], // docstring @method
340
+ [ /(\s+) \* @description (.*)/g, '$1$2' ], // docstring description
341
+ [ /\s+\* @name .*/g, '' ], // docstring @name
342
+ [ /(\s+) \* @returns/g, '$1:returns:' ], // docstring return
343
+ [ /(\s+ \* @param \{[a-z]+\} )([a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+) (.*)/g, '$1$2[\'$3\'] $4' ], // docstring params.anything
344
+ [ /(\s+) \* @([a-z]+) \{([a-z]+)\} ([a-zA-Z0-9_\-\.\[\]\']+)/g, '$1:$2 $3 $4:' ], // docstring param
345
+ ])
346
+ }
347
+
348
+ getPython2Regexes () {
349
+ return [
350
+ [ /.+asyncio\.gather.+\n/g, '' ], // remove line entirely
351
+ [ /(\s)await(\s)/g, '$1' ]
352
+ ]
353
+ }
354
+
355
+ getSyncPHPRegexes () {
356
+ return [
357
+ [ /.+(\$[a-zA-Z0-9_]+)\s*=\s*yield\s+\1;\n/g, '' ], // delete yield all promises line
358
+ [ /\byield /g, '' ]
359
+ ]
360
+ }
361
+
362
+ getPHPRegexes () {
363
+ return [
364
+ //
365
+ // Curly-braces are used for both dictionaries in the code as well as for the url-imploded params.
366
+ // For example: https://docs.ccxt.com/en/latest/manual.html#implicit-api-methods
367
+ //
368
+ // There's a conflict between the curly braces that have to be converted from dictionaries to PHP-arrays and
369
+ // the curly braces used for url-imploded params that should not be touched.
370
+ //
371
+ // The transpiler takes all non-spaced strings in curly braces {likeThis} and converts them to ~likeThis~.
372
+ // That is done to avoid changing the curly braces into the array() in PHP.
373
+ // This way we protect the url-imploded params from being touched by the regexes that will follow.
374
+ // That conversion is done first-thing, at the very early stage of transpilation.
375
+ // The regexes are applied in the order they're listed, top-down.
376
+ //
377
+ // A dictionary in curly braces will never have those curly braces attached to the contents of the dictionary.
378
+ // There will always be a space like { 'a': b, 'c': d }.
379
+ // Hence, the remaining non-converted curly-brace dictionaries will have to be converted to arrays in PHP.
380
+ // That is done in the middle of the transpilation process.
381
+ //
382
+ // The last step is to convert those "saved embedded/imploded url-params substitutions" from ~likeThis~ back to {likeThis}.
383
+ // That is done at the very last regex steps.
384
+ // All of that is a workaround for PHP-arrays vs dictionaries vs url-imploded params in other langs.
385
+ //
386
+ [ /\{([a-zA-Z0-9_-]+?)\}/g, '~$1~' ], // resolve the "arrays vs url params" conflict (both are in {}-brackets)
387
+ [ /\[(.*)\]\{(@link .*)\}/g, '~$2 $1~' ], // docstring item with link
388
+ [ /\s+\* @method/g, '' ], // docstring @method
389
+ [ /(\s+)\* @description (.*)/g, '$1\* $2' ], // docstring description
390
+ [ /\s+\* @name .*/g, '' ], // docstring @name
391
+ [ /(\s+)\* @returns/g, '$1\* @return' ], // docstring return
392
+ [ /\!Array\.isArray\s*\(([^\)]+)\)/g, "gettype($1) === 'array' && count(array_filter(array_keys($1), 'is_string')) != 0" ],
393
+ [ /Array\.isArray\s*\(([^\)]+)\)/g, "gettype($1) === 'array' && count(array_filter(array_keys($1), 'is_string')) == 0" ],
394
+ [ /([^\(\s]+)\s+instanceof\s+String/g, 'is_string($1)' ],
395
+
396
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'undefined\'/g, '$1[$2] === null' ],
397
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'undefined\'/g, '$1[$2] !== null' ],
398
+ [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'undefined\'/g, '$1 === null' ],
399
+ [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'undefined\'/g, '$1 !== null' ],
400
+ [ /typeof\s+(.+?)\s+\=\=\=?\s+\'undefined\'/g, '$1 === null' ],
401
+ [ /typeof\s+(.+?)\s+\!\=\=?\s+\'undefined\'/g, '$1 !== null' ],
402
+
403
+ [ /([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+undefined/g, '$1[$2] === null' ],
404
+ [ /([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+undefined/g, '$1[$2] !== null' ],
405
+ [ /([^\s]+)\s+\=\=\=?\s+undefined/g, '$1 === null' ],
406
+ [ /([^\s]+)\s+\!\=\=?\s+undefined/g, '$1 !== null' ],
407
+ [ /(.+?)\s+\=\=\=?\s+undefined/g, '$1 === null' ],
408
+ [ /(.+?)\s+\!\=\=?\s+undefined/g, '$1 !== null' ],
409
+
410
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'string\'/g, "gettype($1[$2]) === 'string'" ],
411
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'string\'/g, "gettype($1[$2]) !== 'string'" ],
412
+ [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'string\'/g, "gettype($1) === 'string'" ],
413
+ [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'string\'/g, "gettype($1) !== 'string'" ],
414
+
415
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'object\'/g, "gettype($1[$2]) === 'array'" ],
416
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'object\'/g, "gettype($1[$2]) !== 'array'" ],
417
+ [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'object\'/g, "gettype($1) === 'array'" ],
418
+ [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'object\'/g, "gettype($1) !== 'array'" ],
419
+
420
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'number\'/g, "(is_float($1[$2]) || is_int($1[$2]))" ], // same as above but for number
421
+ [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'number\'/g, "!(is_float($1[$2]) || is_int($1[$2]))" ],
422
+ [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'number\'/g, "(is_float($1) || is_int($1))" ],
423
+ [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'number\'/g, "!(is_float($1) || is_int($1))" ],
424
+
425
+ [ /undefined/g, 'null' ],
426
+ [ /this\.extend\s/g, 'array_merge' ],
427
+ [ /this\.stringToBinary\s*\((.*)\)/g, '$1' ],
428
+ [ /this\.stringToBase64\s/g, 'base64_encode' ],
429
+ [ /this\.binaryToBase16\s/g, 'bin2hex' ],
430
+ [ /this\.base64ToBinary\s/g, 'base64_decode' ],
431
+ [ /this\.base64ToString\s/g, 'base64_decode' ],
432
+ [ /Promise\.all\s*\(([^\)]+)\)/g, '$1' ],
433
+ // deepExtend is commented for PHP because it does not overwrite linear arrays
434
+ // a proper \ccxt\Exchange::deep_extend() base method is implemented instead
435
+ // [ /this\.deepExtend\s/g, 'array_replace_recursive'],
436
+ [ /(\w+)\.shift\s*\(\)/g, 'array_shift($1)' ],
437
+ [ /(\w+)\.pop\s*\(\)/g, 'array_pop($1)' ],
438
+ [ /Number\.MAX_SAFE_INTEGER/g, 'PHP_INT_MAX' ],
439
+ [ /Precise\.stringAdd\s/g, 'Precise::string_add' ],
440
+ [ /Precise\.stringDiv\s/g, 'Precise::string_div' ],
441
+ [ /Precise\.stringMul\s/g, 'Precise::string_mul' ],
442
+ [ /Precise\.stringSub\s/g, 'Precise::string_sub' ],
443
+ [ /Precise\.stringAbs\s/g, 'Precise::string_abs' ],
444
+ [ /Precise\.stringNeg\s/g, 'Precise::string_neg' ],
445
+ [ /Precise\.stringMod\s/g, 'Precise::string_mod' ],
446
+ [ /Precise\.stringEquals\s/g, 'Precise::string_equals' ],
447
+ [ /Precise\.stringEq\s/g, 'Precise::string_eq' ],
448
+ [ /Precise\.stringMin\s/g, 'Precise::string_min' ],
449
+ [ /Precise\.stringMax\s/g, 'Precise::string_max' ],
450
+ [ /Precise\.stringGt\s/g, 'Precise::string_gt' ],
451
+ [ /Precise\.stringGe\s/g, 'Precise::string_ge' ],
452
+ [ /Precise\.stringLt\s/g, 'Precise::string_lt' ],
453
+ [ /Precise\.stringLe\s/g, 'Precise::string_le' ],
454
+
455
+ // insert common regexes in the middle (critical)
456
+ ].concat (this.getCommonRegexes ()).concat ([
457
+
458
+ [ /this\./g, '$this->' ],
459
+ [ / this;/g, ' $this;' ],
460
+ [ /([^'])this_\./g, '$1$this_->' ],
461
+ [ /([^'])\{\}/g, '$1array()' ],
462
+ [ /([^'])\[\]/g, '$1array()' ],
463
+
464
+ // add {}-array syntax conversions up to 20 levels deep in the same line
465
+ ]).concat ([ ... Array (20) ].map (x => [ /\{([^\n\}]+)\}/g, 'array($1)' ] )).concat ([
466
+ [ /\[\s*([^\]]+)\s\]\s=/g, 'list($1) =' ],
467
+ [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s\[\s*([^\]]+)\s\]/g, '$1list($2)' ],
468
+ [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s\{\s*([^\}]+)\s\}/g, '$1array_values(list($2))' ],
469
+ [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s/g, '$1' ],
470
+ [ /Object\.keys\s*\((.*)\)\.length/g, '$1' ],
471
+ [ /Object\.keys\s*\((.*)\)/g, 'is_array($1) ? array_keys($1) : array()' ],
472
+ [ /Object\.values\s*\((.*)\)/g, 'is_array($1) ? array_values($1) : array()' ],
473
+ [ /([^\s]+\s*\(\))\.toString \(\)/g, '(string) $1' ],
474
+ [ /([^\s]+)\.toString \(\)/g, '(string) $1' ],
475
+ [ /throw new Error \((.*)\)/g, 'throw new \\Exception($1)' ],
476
+ [ /throw new ([\S]+) \((.*)\)/g, 'throw new $1($2)' ],
477
+ [ /throw ([\S]+)\;/g, 'throw $$$1;' ],
478
+ [ '([^a-z]+) (' + Object.keys (errors).join ('|') + ')([^\\s])', "$1 '\\\\ccxt\\\\$2'$3" ],
479
+ [ /\}\s+catch \(([\S]+)\) {/g, '} catch (Exception $$$1) {' ],
480
+ [ /for\s+\(([a-zA-Z0-9_]+)\s*=\s*([^\;\s]+\s*)\;[^\<\>\=]+(\<=|\>=|<|>)\s*(.*)\.length\s*\;([^\)]+)\)\s*{/g, 'for ($1 = $2; $1 $3 count($4);$5) {' ],
481
+ [ /for\s+\(([a-zA-Z0-9_]+)\s*=\s*([^\;\s]+\s*)\;[^\<\>\=]+(\<=|\>=|<|>)\s*(.*)\s*\;([^\)]+)\)\s*{/g, 'for ($1 = $2; $1 $3 $4;$5) {' ],
482
+ [ /([^\s]+)\.length\;/g, 'is_array($1) ? count($1) : 0;' ],
483
+ [ /\.push\s*\(([\s\S]+?)\)\;/g, '[] = $1;' ],
484
+ [ /(\b)await(\b)/g, 'yield' ],
485
+ [ /([\S])\: /g, '$1 => ' ],
486
+
487
+ // add {}-array syntax conversions up to 20 levels deep
488
+ ]).concat ([ ... Array (20) ].map (x => [ /\{([^\{]+?)\}([^\s])/g, 'array($1)$2' ])).concat ([
489
+
490
+ [ /\[\s*([^\]]+?)\s*\]\.join\s*\(\s*([^\)]+?)\s*\)/g, "implode($2, array($1))" ],
491
+
492
+ // add []-array syntax conversions up to 20 levels deep
493
+ ]).concat ([ ... Array (20) ].map (x => [ /\[(\s[^\]]+?\s)\]/g, 'array($1)' ])).concat ([
494
+
495
+ [ /JSON\.stringify/g, 'json_encode' ],
496
+ [ /JSON\.parse\s+\(([^\)]+)\)/g, 'json_decode($1, $$as_associative_array = true)' ],
497
+ // [ /\'([^\']+)\'\.sprintf\s*\(([^\)]+)\)/g, "sprintf ('$1', $2)" ],
498
+ [ /([^\s]+)\.toFixed\s*\(([0-9]+)\)/g, "sprintf('%.$2f', $1)" ],
499
+ [ /([^\s]+)\.toFixed\s*\(([^\)]+)\)/g, "sprintf('%.' . $2 . 'f', $1)" ],
500
+ [ /parseFloat\s/g, 'floatval'],
501
+ [ /parseInt\s/g, 'intval'],
502
+ [ / \+ (?!\d)/g, ' . ' ],
503
+ [ / \+\= (?!\d)/g, ' .= ' ],
504
+ [ /([^\s\(]+(?:\s*\(.+\))?)\.toUpperCase\s*\(\)/g, 'strtoupper($1)' ],
505
+ [ /([^\s\(]+(?:\s*\(.+\))?)\.toLowerCase\s*\(\)/g, 'strtolower($1)' ],
506
+ // [ /([^\s\(]+(?:\s*\(.+\))?)\.replaceAll\s*\(([^)]+)\)/g, 'str_replace($2, $1)' ], // still not a part of the standard in Node.js 13
507
+ [ /([^\s\(]+(?:\s*\(.+\))?)\.replace\s*\(([^)]+)\)/g, 'str_replace($2, $1)' ],
508
+ [ /this\[([^\]+]+)\]/g, '$$this->$$$1' ],
509
+ [ /([^\s\(]+).slice \(([^\)\:,]+)\)/g, 'mb_substr($1, $2)' ],
510
+ [ /([^\s\(]+).slice \(([^\,\)]+)\,\s*([^\)]+)\)/g, 'mb_substr($1, $2, $3 - $2)' ],
511
+ [ /([^\s\(]+).split \(('[^']*'|[^\,]+?)\)/g, 'explode($2, $1)' ],
512
+ [ /([^\s\(]+)\.length/g, 'strlen($1)' ],
513
+ [ /Math\.floor\s*\(([^\)]+)\)/g, '(int) floor($1)' ],
514
+ [ /Math\.abs\s*\(([^\)]+)\)/g, 'abs($1)' ],
515
+ [ /Math\.round\s*\(([^\)]+)\)/g, '(int) round($1)' ],
516
+ [ /Math\.ceil\s*\(([^\)]+)\)/g, '(int) ceil($1)' ],
517
+ [ /Math\.pow\s*\(([^\)]+)\)/g, 'pow($1)' ],
518
+ [ /Math\.log/g, 'log' ],
519
+ [ /([^\(\s]+)\s+%\s+([^\s\,\;\)]+)/g, 'fmod($1, $2)' ],
520
+ [ /\(([^\s\(]+)\.indexOf\s*\(([^\)]+)\)\s*\>\=\s*0\)/g, '(mb_strpos($1, $2) !== false)' ],
521
+ [ /([^\s\(]+)\.indexOf\s*\(([^\)]+)\)\s*\>\=\s*0/g, 'mb_strpos($1, $2) !== false' ],
522
+ [ /([^\s\(]+)\.indexOf\s*\(([^\)]+)\)\s*\<\s*0/g, 'mb_strpos($1, $2) === false' ],
523
+ [ /([^\s\(]+)\.indexOf\s*\(([^\)]+)\)/g, 'mb_strpos($1, $2)' ],
524
+ [ /\(([^\s\(]+)\sin\s([^\)]+)\)/g, '(is_array($2) && array_key_exists($1, $2))' ],
525
+ [ /([^\s]+)\.join\s*\(\s*([^\)]+?)\s*\)/g, 'implode($2, $1)' ],
526
+ [ 'new ccxt\\.', 'new \\ccxt\\' ], // a special case for test_exchange_datetime_functions.php (and for other files, maybe)
527
+ [ /Math\.(max|min)/g, '$1' ],
528
+ [ /console\.log/g, 'var_dump'],
529
+ [ /process\.exit/g, 'exit'],
530
+ [ /super\./g, 'parent::'],
531
+ [ /\sdelete\s([^\n]+)\;/g, ' unset($1);' ],
532
+ [ /\~([@\.\s+\:\/#\-a-zA-Z0-9_-]+?)\~/g, '{$1}' ], // resolve the "arrays vs url params" conflict (both are in {}-brackets)
533
+ ])
534
+ }
535
+
536
+ getBaseClass () {
537
+ return new Exchange ()
538
+ }
539
+
540
+ getBaseMethods () {
541
+ const baseExchange = this.getBaseClass ()
542
+ let object = baseExchange
543
+ let properties = []
544
+ while (object !== Object.prototype) {
545
+ properties = properties.concat (Object.getOwnPropertyNames (object))
546
+ object = Object.getPrototypeOf (object)
547
+ }
548
+ return properties.filter (x => typeof baseExchange[x] === 'function')
549
+ }
550
+
551
+ getPythonBaseMethods () {
552
+ return this.getBaseMethods ()
553
+ }
554
+
555
+ getPHPBaseMethods () {
556
+ return this.getBaseMethods ()
557
+ }
558
+
559
+ //-------------------------------------------------------------------------
560
+ // the following common headers are used for transpiled tests
561
+
562
+ getPythonPreamble () {
563
+ return [
564
+ "import os",
565
+ "import sys",
566
+ "",
567
+ "root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))",
568
+ "sys.path.append(root)",
569
+ "",
570
+ "# ----------------------------------------------------------------------------",
571
+ "",
572
+ "# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:",
573
+ "# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code",
574
+ "",
575
+ "# ----------------------------------------------------------------------------",
576
+ "",
577
+ ].join ("\n")
578
+ }
579
+
580
+ getPHPPreamble (include = true) {
581
+ return [
582
+ "<?php",
583
+ "namespace ccxt;",
584
+ include ? "include_once (__DIR__.'/../../ccxt.php');" : "",
585
+ "// ----------------------------------------------------------------------------",
586
+ "",
587
+ "// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:",
588
+ "// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code",
589
+ "",
590
+ "// -----------------------------------------------------------------------------",
591
+ "",
592
+ ].join ("\n")
593
+ }
594
+
595
+ // ------------------------------------------------------------------------
596
+ // a helper to apply an array of regexes and substitutions to text
597
+ // accepts an array like [ [ regex, substitution ], ... ]
598
+
599
+ regexAll (text, array) {
600
+ for (const i in array) {
601
+ let regex = array[i][0]
602
+ const flags = (typeof regex === 'string') ? 'g' : undefined
603
+ regex = new RegExp (regex, flags)
604
+ text = text.replace (regex, array[i][1])
605
+ }
606
+ return text
607
+ }
608
+
609
+ // ========================================================================
610
+ // one-time helpers
611
+
612
+ createPythonClassDeclaration (className, baseClass) {
613
+ return 'class ' + className + '(' + baseClass + '):'
614
+ }
615
+
616
+ createPythonClassHeader (imports, bodyAsString) {
617
+ return [
618
+ "# -*- coding: utf-8 -*-",
619
+ "",
620
+ "# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:",
621
+ "# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code",
622
+ "",
623
+ ... imports,
624
+ ]
625
+ }
626
+
627
+ createPythonClassImports (baseClass, async = false) {
628
+
629
+ const baseClasses = {
630
+ 'Exchange': 'base.exchange',
631
+ }
632
+
633
+ async = (async ? '.async_support' : '')
634
+
635
+ return [
636
+ (baseClass.indexOf ('ccxt.') === 0) ?
637
+ ('import ccxt' + async + ' as ccxt') :
638
+ ('from ccxt' + async + '.' + safeString (baseClasses, baseClass, baseClass) + ' import ' + baseClass)
639
+ ]
640
+ }
641
+
642
+ createPythonClass (className, baseClass, body, methods, async = false) {
643
+
644
+ const pythonStandardLibraries = {
645
+ 'hashlib': 'hashlib',
646
+ 'math': 'math',
647
+ 'json.loads': 'json',
648
+ 'sys': 'sys',
649
+ }
650
+
651
+ const baseClasses = {
652
+ 'Exchange': 'base.exchange',
653
+ }
654
+
655
+ async = (async ? '.async_support' : '')
656
+
657
+ const imports = this.createPythonClassImports (baseClass, async)
658
+
659
+ let bodyAsString = body.join ("\n")
660
+
661
+ let header = this.createPythonClassHeader (imports, bodyAsString)
662
+
663
+ const libraries = []
664
+
665
+ for (let library in pythonStandardLibraries) {
666
+ const regex = new RegExp ("[^\\'\\\"a-zA-Z]" + library + "[^\\'\\\"a-zA-Z]")
667
+ if (bodyAsString.match (regex))
668
+ libraries.push ('import ' + pythonStandardLibraries[library])
669
+ }
670
+
671
+ if (body.indexOf ('numbers') >= 0) {
672
+ libraries.push ('import numbers')
673
+ }
674
+
675
+ const errorImports = []
676
+
677
+ for (let error in errors) {
678
+ const regex = new RegExp ("[^\\'\"]" + error + "[^\\'\"]")
679
+ if (bodyAsString.match (regex)) {
680
+ errorImports.push ('from ccxt.base.errors import ' + error)
681
+ }
682
+ }
683
+
684
+ const precisionImports = []
685
+
686
+ for (let constant in precisionConstants) {
687
+ if (bodyAsString.indexOf (constant) >= 0) {
688
+ precisionImports.push ('from ccxt.base.decimal_to_precision import ' + constant)
689
+ }
690
+ }
691
+ if (bodyAsString.match (/[\s(]Precise/)) {
692
+ precisionImports.push ('from ccxt.base.precise import Precise')
693
+ }
694
+ const asyncioImports = []
695
+ if (bodyAsString.match (/asyncio/)) {
696
+ asyncioImports.push ('import asyncio')
697
+ }
698
+
699
+ header = header.concat (asyncioImports, libraries, errorImports, precisionImports)
700
+
701
+ methods = methods.concat (this.getPythonBaseMethods ())
702
+
703
+ for (let method of methods) {
704
+ const regex = new RegExp ('self\\.(' + method + ')([^a-zA-Z0-9_])', 'g')
705
+ bodyAsString = bodyAsString.replace (regex,
706
+ (match, p1, p2) => ('self.' + unCamelCase (p1) + p2))
707
+ }
708
+
709
+ header.push ("\n\n" + this.createPythonClassDeclaration (className, baseClass))
710
+
711
+ const footer = [
712
+ '', // footer (last empty line)
713
+ ]
714
+
715
+ const result = header.join ("\n") + "\n" + bodyAsString + "\n" + footer.join ('\n')
716
+ return result
717
+ }
718
+
719
+ // ========================================================================
720
+ // exchange capabilities ordering
721
+
722
+ sortExchangeCapabilities (code) {
723
+ const lineBreak = '\n';
724
+ const capabilitiesObjectRegex = /(?<='has': {[\n])([^|})]*)(?=\n(\s+}))/;
725
+ const found = capabilitiesObjectRegex.exec (code);
726
+ if (found === null) {
727
+ return false // capabilities not found
728
+ }
729
+ let capabilities = found[0].split (lineBreak);
730
+ const exchange = new Exchange ()
731
+ const sortingOrder = {
732
+ 'CORS': 'undefined,',
733
+ 'spot': 'true,',
734
+ 'margin': 'undefined,',
735
+ 'swap': 'undefined,',
736
+ 'future': 'undefined,',
737
+ 'option': 'undefined,',
738
+ // then everything else
739
+ }
740
+ const features = {}
741
+ let indentation = ' ' // 16 spaces
742
+ for (let i = 0; i < capabilities.length; i++) {
743
+ const capability = capabilities[i]
744
+ const match = capability.match (/(\s+)\'(.+)\': (.+)$/)
745
+ if (match) {
746
+ indentation = match[1]
747
+ const feature = match[2]
748
+ const value = match[3]
749
+ features[feature] = value
750
+ }
751
+ }
752
+ let keys = Object.keys (features)
753
+ keys.sort ((a, b) => a.localeCompare (b))
754
+ const allKeys = Object.keys (sortingOrder).concat (keys)
755
+ for (let i = 0; i < allKeys.length; i++) {
756
+ const key = allKeys[i]
757
+ sortingOrder[key] = (key in features) ? features[key] : sortingOrder[key]
758
+ }
759
+ const result = Object.entries (sortingOrder).map (([ key, value ]) => indentation + "'" + key + "': " + value).join (lineBreak)
760
+ if (result === found[0]) {
761
+ return false
762
+ }
763
+ return code.replace (capabilitiesObjectRegex, result)
764
+ }
765
+
766
+ // ------------------------------------------------------------------------
767
+
768
+ createPHPClassDeclaration (className, baseClass) {
769
+ return 'class ' + className + ' extends ' + baseClass + ' {'
770
+ }
771
+
772
+ createPHPClassHeader (className, baseClass, bodyAsString, namespace) {
773
+ return [
774
+ "<?php",
775
+ "",
776
+ "namespace " + namespace + ";",
777
+ "",
778
+ "// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:",
779
+ "// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code",
780
+ "",
781
+ "use Exception; // a common import",
782
+ ]
783
+ }
784
+
785
+ createPHPClass (className, baseClass, body, methods, async = false) {
786
+
787
+ let bodyAsString = body.join ("\n")
788
+
789
+ let header = this.createPHPClassHeader (className, baseClass, bodyAsString, async ? 'ccxt\\async' : 'ccxt')
790
+
791
+ const errorImports = []
792
+
793
+ for (let error in errors) {
794
+ const regex = new RegExp ("[^'\"]" + error + "[^'\"]")
795
+ if (bodyAsString.match (regex)) {
796
+ errorImports.push ('use \\ccxt\\' + error + ';')
797
+ }
798
+ }
799
+
800
+ const precisionImports = []
801
+
802
+ if (async && bodyAsString.match (/[\s(]Precise/)) {
803
+ precisionImports.push ('use \\ccxt\\Precise;')
804
+ }
805
+
806
+ header = header.concat (errorImports).concat (precisionImports)
807
+
808
+ methods = methods.concat (this.getPHPBaseMethods ())
809
+
810
+ for (let method of methods) {
811
+ const regex = new RegExp ('\\$this->(' + method + ')\\s?(\\(|[^a-zA-Z0-9_])', 'g')
812
+ bodyAsString = bodyAsString.replace (regex,
813
+ (match, p1, p2) => {
814
+ return ((p2 === '(') ?
815
+ ('$this->' + unCamelCase (p1) + p2) : // support direct php calls
816
+ ("array($this, '" + unCamelCase (p1) + "')" + p2)) // as well as passing instance methods as callables
817
+ })
818
+ }
819
+
820
+ header.push ("\n" + this.createPHPClassDeclaration (className, baseClass))
821
+
822
+ const footer =[
823
+ "}\n",
824
+ ]
825
+
826
+ const result = header.join ("\n") + "\n" + bodyAsString + "\n" + footer.join ('\n')
827
+ return result
828
+ }
829
+
830
+ // ========================================================================
831
+
832
+ transpileJavaScriptToPython3 ({ js, className, removeEmptyLines }) {
833
+
834
+ // transpile JS → Python 3
835
+ let python3Body = this.regexAll (js, this.getPythonRegexes ())
836
+
837
+ if (removeEmptyLines)
838
+ python3Body = python3Body.replace (/$\s*$/gm, '')
839
+
840
+ python3Body = python3Body.replace (/\'([абвгдеёжзийклмнопрстуфхцчшщъыьэюя服务端忙碌]+)\'/gm, "u'$1'")
841
+
842
+ // special case for Python OrderedDicts
843
+ let orderedDictRegex = /\.ordered\s+\(\{([^\}]+)\}\)/g
844
+ let orderedDictMatches = undefined
845
+ while (orderedDictMatches = orderedDictRegex.exec (python3Body)) {
846
+ let replaced = orderedDictMatches[1].replace (/^(\s+)([^\:]+)\:\s*([^\,]+)\,$/gm, '$1($2, $3),')
847
+ python3Body = python3Body.replace (orderedDictRegex, '\.ordered([' + replaced + '])')
848
+ }
849
+
850
+ // snake case function names
851
+ python3Body = python3Body.replace (/def (\w+)/g, (match, group1) => 'def ' + unCamelCase (group1))
852
+
853
+ // special case for Python super
854
+ if (className) {
855
+ python3Body = python3Body.replace (/super\./g, 'super(' + className + ', self).')
856
+ }
857
+
858
+ return python3Body
859
+ }
860
+
861
+ // ------------------------------------------------------------------------
862
+
863
+ transpilePython3ToPython2 (py) {
864
+
865
+ // remove await from Python sync body (transpile Python async → Python sync)
866
+ let python2Body = this.regexAll (py, this.getPython2Regexes ())
867
+
868
+ return python2Body
869
+ }
870
+
871
+ // ------------------------------------------------------------------------
872
+
873
+ transpileAsyncPHPToSyncPHP (php) {
874
+
875
+ // remove yield from php body
876
+ return this.regexAll (php, this.getSyncPHPRegexes ())
877
+ }
878
+
879
+ // ------------------------------------------------------------------------
880
+
881
+
882
+ transpileJavaScriptToPHP ({ js, variables }) {
883
+
884
+ // match all local variables (let, const or var)
885
+ let localVariablesRegex = /(?:^|[^a-zA-Z0-9_])(?:let|const|var)\s+(?:\[([^\]]+)\]|([a-zA-Z0-9_]+))/g // local variables
886
+
887
+ let allVariables = (variables || []).map (x => x); // clone the array
888
+ // process the variables created in destructuring assignments as well
889
+ let localVariablesMatches
890
+ while (localVariablesMatches = localVariablesRegex.exec (js)) {
891
+ if (localVariablesMatches[1]) {
892
+ // this is a destructuring assignment like
893
+ // let [ a, b, c ] = 'a-b-c'.split ('-')
894
+ let matches = localVariablesMatches[1].trim ().split (', ') // split the destructuring assignment by comma
895
+ matches.forEach (x => allVariables.push (x.trim ())) // trim each variable name
896
+ } else {
897
+ // this is a single variable assignment
898
+ allVariables.push (localVariablesMatches[2].trim ()) // add it to the list of local variables
899
+ }
900
+ }
901
+
902
+ // match all variables instantiated in the catch()-block of a try-catch clause
903
+ let catchClauseRegex = /catch \(([^)]+)\)/g
904
+ let catchClauseMatches
905
+ while (catchClauseMatches = catchClauseRegex.exec (js)) {
906
+ allVariables.push (catchClauseMatches[1])
907
+ }
908
+
909
+ // match all variables instantiated as function parameters
910
+ let functionParamRegex = /function\s*(\w+)\s*\(([^)]+)\)/g
911
+ js = js.replace (functionParamRegex, (match, group1, group2) => 'function ' + unCamelCase (group1) + '(' + group2 + ')')
912
+ let functionParamVariables
913
+ while (functionParamVariables = functionParamRegex.exec (js)) {
914
+ const match = functionParamVariables[2]
915
+ const tokens = match.split (', ')
916
+ allVariables = allVariables.concat (tokens)
917
+ }
918
+
919
+ allVariables = allVariables.map (error => this.regexAll (error, this.getCommonRegexes ()))
920
+
921
+ // append $ to all variables in the method (PHP syntax demands $ at the beginning of a variable name)
922
+ let phpVariablesRegexes = allVariables.map (x => [ "(^|[^$$a-zA-Z0-9\\.\\>'\"_/])" + x + "([^a-zA-Z0-9'_/])", '$1$$' + x + '$2' ])
923
+
924
+ // support for php syntax for object-pointer dereference
925
+ // convert all $variable.property to $variable->property
926
+ let variablePropertiesRegexes = allVariables.map (x => [ "(^|[^a-zA-Z0-9\\.\\>'\"_])" + x + '\\.', '$1' + x + '->' ])
927
+
928
+ // transpile JS → PHP
929
+ const phpRegexes = this.getPHPRegexes ()
930
+ let phpBody = this.regexAll (js, phpRegexes.concat (phpVariablesRegexes).concat (variablePropertiesRegexes))
931
+
932
+ return phpBody
933
+ }
934
+
935
+ // ------------------------------------------------------------------------
936
+
937
+ transpileJavaScriptToPythonAndPHP (args) {
938
+
939
+ // transpile JS → Python 3
940
+ let python3Body = this.transpileJavaScriptToPython3 (args)
941
+
942
+ // remove await from Python sync body (transpile Python async → Python sync)
943
+ let python2Body = this.transpilePython3ToPython2 (python3Body)
944
+
945
+ // transpile JS → Async PHP
946
+ let phpAsyncBody = this.transpileJavaScriptToPHP (args)
947
+
948
+ // transpile async PHP -> sync PHP
949
+ let phpBody = this.transpileAsyncPHPToSyncPHP (phpAsyncBody)
950
+
951
+ return { python3Body, python2Body, phpBody, phpAsyncBody }
952
+ }
953
+
954
+ //-----------------------------------------------------------------------------
955
+
956
+ transpilePythonAsyncToSync () {
957
+
958
+ const async = './python/ccxt/test/test_async.py'
959
+ const sync = './python/ccxt/test/test_sync.py'
960
+ log.magenta ('Transpiling ' + async .yellow + ' → ' + sync.yellow)
961
+ const fileContents = fs.readFileSync (async, 'utf8')
962
+ let lines = fileContents.split ("\n")
963
+
964
+ lines = lines.filter (line => ![ 'import asyncio' ].includes (line))
965
+ .map (line => {
966
+ return (
967
+ line.replace ('asyncio.get_event_loop().run_until_complete(main())', 'main()')
968
+ .replace ('asyncio.run(main())', 'main()')
969
+ .replace ('import ccxt.async_support as ccxt', 'import ccxt')
970
+ .replace (/.*token\_bucket.*/g, '')
971
+ .replace ('await asyncio.sleep', 'time.sleep')
972
+ .replace ('async ', '')
973
+ .replace ('await ', ''))
974
+ })
975
+
976
+ // lines.forEach (line => log (line))
977
+
978
+ function deleteFunction (f, from) {
979
+ // the following regexes make a technical error
980
+ // since it won't cut away a single function
981
+ // it will delete everything up to the beginning of the next comment
982
+ const re1 = new RegExp ('def ' + f + '[^\#]+', 'g')
983
+ const re2 = new RegExp ('[\\s]+' + f + '\\(exchange\\)', 'g')
984
+ return from.replace (re1, '').replace (re2, '')
985
+ }
986
+
987
+ let newContents = lines.join ('\n')
988
+
989
+ newContents = deleteFunction ('test_tickers_async', newContents)
990
+ newContents = deleteFunction ('test_l2_order_books_async', newContents)
991
+
992
+ fs.truncateSync (sync)
993
+ fs.writeFileSync (sync, newContents)
994
+ }
995
+
996
+ //-----------------------------------------------------------------------------
997
+
998
+ transpilePhpAsyncToSync () {
999
+
1000
+ const async = './php/test/test_async.php'
1001
+ const sync = './php/test/test_sync.php'
1002
+ log.magenta ('Transpiling ' + async .yellow + ' → ' + sync.yellow)
1003
+ const fileContents = fs.readFileSync (async, 'utf8')
1004
+ const syncBody = this.transpileAsyncPHPToSyncPHP (fileContents)
1005
+
1006
+ const phpSyncRegexes = [
1007
+ [ /\$kernel->execute\(\$main\)/, '\$main()' ],
1008
+ [ /\$kernel->run\(\);\n/, '' ],
1009
+ [ /\$kernel = async\\Exchange::get_kernel\(\);\n/, '' ],
1010
+ [ /ccxt\\\\async/, 'ccxt' ],
1011
+ ]
1012
+
1013
+ const newContents = this.regexAll (syncBody, phpSyncRegexes)
1014
+
1015
+ fs.truncateSync (sync)
1016
+ fs.writeFileSync (sync, newContents)
1017
+ }
1018
+
1019
+ // ------------------------------------------------------------------------
1020
+
1021
+ getExchangeClassDeclarationMatches (contents) {
1022
+ return contents.match (/^module\.exports\s*=\s*class\s+([\S]+)\s+extends\s+([\S]+)\s+{([\s\S]+?)^};*/m)
1023
+ }
1024
+
1025
+ // ------------------------------------------------------------------------
1026
+
1027
+ transpileDerivedExchangeClass (contents, methodNames = undefined) {
1028
+
1029
+ const [ _, className, baseClass, methodMatches ] = this.getExchangeClassDeclarationMatches (contents)
1030
+
1031
+ const methods = methodMatches.trim ().split (/\n\s*\n/)
1032
+
1033
+ let python2 = []
1034
+ let python3 = []
1035
+ let php = []
1036
+ let phpAsync = []
1037
+
1038
+ methodNames = [] // methodNames || []
1039
+
1040
+ // run through all methods
1041
+ for (let i = 0; i < methods.length; i++) {
1042
+ // parse the method signature
1043
+ let part = methods[i].trim ()
1044
+ let lines = part.split ("\n")
1045
+ let signature = lines[0].trim ()
1046
+ let methodSignatureRegex = /(async |)([\S]+)\s\(([^)]*)\)\s*{/ // signature line
1047
+ let matches = methodSignatureRegex.exec (signature)
1048
+
1049
+ if (!matches) {
1050
+ log.red (methods[i])
1051
+ log.yellow.bright ("\nMake sure your methods don't have empty lines!\n")
1052
+ }
1053
+
1054
+ // async or not
1055
+ let keyword = matches[1]
1056
+
1057
+ // method name
1058
+ let method = matches[2]
1059
+
1060
+ methodNames.push (method)
1061
+
1062
+ method = unCamelCase (method)
1063
+
1064
+ // method arguments
1065
+ let args = matches[3].trim ()
1066
+
1067
+ // extract argument names and local variables
1068
+ args = args.length ? args.split (',').map (x => x.trim ()) : []
1069
+
1070
+ // get names of all method arguments for later substitutions
1071
+ let variables = args.map (arg => arg.split ('=').map (x => x.trim ()) [0])
1072
+
1073
+ // add $ to each argument name in PHP method signature
1074
+ let phpArgs = args.join (', $').trim ().replace (/undefined/g, 'null').replace (/\{\}/g, 'array ()')
1075
+ phpArgs = phpArgs.length ? ('$' + phpArgs) : ''
1076
+
1077
+ // remove excessive spacing from argument defaults in Python method signature
1078
+ let pythonArgs = args.map (x => x.replace (' = ', '='))
1079
+ .join (', ')
1080
+ .replace (/undefined/g, 'None')
1081
+ .replace (/false/g, 'False')
1082
+ .replace (/true/g, 'True')
1083
+
1084
+ // method body without the signature (first line)
1085
+ // and without the closing bracket (last line)
1086
+ let js = lines.slice (1, -1).join ("\n")
1087
+
1088
+ // transpile everything
1089
+ let { python3Body, python2Body, phpBody, phpAsyncBody } = this.transpileJavaScriptToPythonAndPHP ({ js, className, variables, removeEmptyLines: true })
1090
+
1091
+ // compile the final Python code for the method signature
1092
+ let pythonString = 'def ' + method + '(self' + (pythonArgs.length ? ', ' + pythonArgs : '') + '):'
1093
+
1094
+ // compile signature + body for Python sync
1095
+ python2.push ('');
1096
+ python2.push (' ' + pythonString);
1097
+ python2.push (python2Body);
1098
+
1099
+ // compile signature + body for Python async
1100
+ python3.push ('');
1101
+ python3.push (' ' + keyword + pythonString);
1102
+ python3.push (python3Body);
1103
+
1104
+ // compile signature + body for PHP
1105
+ php.push ('');
1106
+ php.push (' public function ' + method + '(' + phpArgs + ') {');
1107
+ php.push (phpBody);
1108
+ php.push (' }')
1109
+
1110
+ phpAsync.push ('');
1111
+ phpAsync.push (' public function ' + method + '(' + phpArgs + ') {');
1112
+ phpAsync.push (phpAsyncBody);
1113
+ phpAsync.push (' }')
1114
+ }
1115
+
1116
+ return {
1117
+
1118
+ // altogether in PHP, async PHP, Python sync and async
1119
+ python2: this.createPythonClass (className, baseClass, python2, methodNames),
1120
+ python3: this.createPythonClass (className, baseClass, python3, methodNames, true),
1121
+ php: this.createPHPClass (className, baseClass, php, methodNames),
1122
+ phpAsync: this.createPHPClass (className, baseClass, phpAsync, methodNames, true),
1123
+
1124
+ className,
1125
+ baseClass,
1126
+ }
1127
+ }
1128
+
1129
+ // ========================================================================
1130
+
1131
+ transpileDerivedExchangeFile (jsFolder, filename, options, force = false) {
1132
+
1133
+ // todo normalize jsFolder and other arguments
1134
+
1135
+ try {
1136
+
1137
+ const { python2Folder, python3Folder, phpFolder, phpAsyncFolder } = options
1138
+ const pythonFilename = filename.replace ('.js', '.py')
1139
+ const phpFilename = filename.replace ('.js', '.php')
1140
+
1141
+ const jsPath = jsFolder + filename
1142
+
1143
+ let contents = fs.readFileSync (jsPath, 'utf8')
1144
+ const sortedExchangeCapabilities = this.sortExchangeCapabilities (contents)
1145
+ if (sortedExchangeCapabilities) {
1146
+ contents = sortedExchangeCapabilities
1147
+ overwriteFile (jsPath, contents)
1148
+ }
1149
+
1150
+ const jsMtime = fs.statSync (jsPath).mtime.getTime ()
1151
+
1152
+ const python2Path = python2Folder ? (python2Folder + pythonFilename) : undefined
1153
+ const python3Path = python3Folder ? (python3Folder + pythonFilename) : undefined
1154
+ const phpPath = phpFolder ? (phpFolder + phpFilename) : undefined
1155
+ const phpAsyncPath = phpAsyncFolder ? (phpAsyncFolder + phpFilename) : undefined
1156
+
1157
+ const python2Mtime = python2Folder ? (fs.existsSync (python2Path) ? fs.statSync (python2Path).mtime.getTime () : 0) : undefined
1158
+ const python3Mtime = python3Path ? (fs.existsSync (python3Path) ? fs.statSync (python3Path).mtime.getTime () : 0) : undefined
1159
+ const phpAsyncMtime = phpAsyncFolder ? (fs.existsSync (phpAsyncPath) ? fs.statSync (phpAsyncPath).mtime.getTime () : 0) : undefined
1160
+ const phpMtime = phpPath ? (fs.existsSync (phpPath) ? fs.statSync (phpPath).mtime.getTime () : 0) : undefined
1161
+
1162
+ if (force ||
1163
+ (python3Folder && (jsMtime > python3Mtime)) ||
1164
+ (phpFolder && (jsMtime > phpMtime)) ||
1165
+ (phpAsyncFolder && (jsMtime > phpAsyncMtime)) ||
1166
+ (python2Folder && (jsMtime > python2Mtime))) {
1167
+ const { python2, python3, php, phpAsync, className, baseClass } = this.transpileDerivedExchangeClass (contents)
1168
+ log.cyan ('Transpiling from', filename.yellow)
1169
+
1170
+ ;[
1171
+ [ python2Folder, pythonFilename, python2 ],
1172
+ [ python3Folder, pythonFilename, python3 ],
1173
+ [ phpFolder, phpFilename, php ],
1174
+ [ phpAsyncFolder, phpFilename, phpAsync ],
1175
+ ].forEach (([ folder, filename, code ]) => {
1176
+ if (folder) {
1177
+ overwriteFile (folder + filename, code)
1178
+ fs.utimesSync (folder + filename, new Date (), new Date (jsMtime))
1179
+ }
1180
+ })
1181
+
1182
+ return { className, baseClass }
1183
+
1184
+ } else {
1185
+
1186
+ const [ _, className, baseClass ] = this.getExchangeClassDeclarationMatches (contents)
1187
+ log.green ('Already transpiled', filename.yellow)
1188
+ return { className, baseClass }
1189
+ }
1190
+
1191
+ } catch (e) {
1192
+
1193
+ log.red ('\nFailed to transpile source code from', filename.yellow)
1194
+ log.red ('See https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md on how to build this library properly\n')
1195
+ throw e // rethrow it
1196
+ }
1197
+ }
1198
+
1199
+ //-------------------------------------------------------------------------
1200
+
1201
+ transpileDerivedExchangeFiles (jsFolder, options, pattern = '.js', force = false) {
1202
+
1203
+ // todo normalize jsFolder and other arguments
1204
+
1205
+ const { python2Folder, python3Folder, phpFolder, phpAsyncFolder } = options
1206
+
1207
+ // exchanges.json accounts for ids included in exchanges.cfg
1208
+ let ids = undefined
1209
+ try {
1210
+ ids = require ('../exchanges.json').ids;
1211
+ } catch (e) {
1212
+ }
1213
+
1214
+ const regex = new RegExp (pattern.replace (/[.*+?^${}()|[\]\\]/g, '\\$&'))
1215
+
1216
+ const classNames = fs.readdirSync (jsFolder)
1217
+ .filter (file => file.match (regex) && (!ids || ids.includes (basename (file, '.js'))))
1218
+ .map (file => this.transpileDerivedExchangeFile (jsFolder, file, options, force))
1219
+
1220
+ const classes = {}
1221
+
1222
+ if (classNames.length === 0) {
1223
+ return null
1224
+ }
1225
+
1226
+ classNames.forEach (({ className, baseClass }) => {
1227
+ classes[className] = baseClass
1228
+ })
1229
+
1230
+ if (classNames.length > 1) {
1231
+
1232
+ function deleteOldTranspiledFiles (folder, pattern) {
1233
+ fs.readdirSync (folder)
1234
+ .filter (file =>
1235
+ !fs.lstatSync (folder + file).isDirectory () &&
1236
+ !(file.replace (pattern, '') in classes) &&
1237
+ !file.match (/^[A-Z_]/))
1238
+ .map (file => folder + file)
1239
+ .forEach (file => log.red ('Deleting ' + file.yellow) && fs.unlinkSync (file))
1240
+ }
1241
+
1242
+ [
1243
+ [ python2Folder, /\.pyc?$/ ],
1244
+ [ python3Folder, /\.pyc?$/ ],
1245
+ [ phpFolder, /\.php$/ ],
1246
+ [ phpAsyncFolder, /\.php$/ ],
1247
+ ].forEach (([ folder, pattern ]) => {
1248
+ if (folder) {
1249
+ deleteOldTranspiledFiles (folder, pattern)
1250
+ }
1251
+ })
1252
+
1253
+ }
1254
+
1255
+ return classes
1256
+ }
1257
+
1258
+ // ========================================================================
1259
+
1260
+ exportTypeScriptClassNames (file, classes) {
1261
+
1262
+ log.bright.cyan ('Exporting TypeScript class names →', file.yellow)
1263
+
1264
+ const regex = /\/[\n]{2}(?: export class [^\s]+ extends [^\s]+ \{\}[\r]?[\n])+/
1265
+ const replacement = "/\n\n" + Object.keys (classes).map (className => {
1266
+ const baseClass = classes[className].replace (/ccxt\.[a-z0-9_]+/, 'Exchange')
1267
+ return ' export class ' + className + ' extends ' + baseClass + " {}"
1268
+ }).join ("\n") + "\n"
1269
+
1270
+ replaceInFile (file, regex, replacement)
1271
+ }
1272
+
1273
+ exportTypeScriptExchangeIds (file, classes) {
1274
+
1275
+ log.bright.cyan ('Exporting TypeScript exchange ids →', file.yellow)
1276
+
1277
+ const regex = /\/[\n]{2} export type ExchangeId =\n(?: \| \'[a-z0-9_]+\'[\r]?[\n])+/
1278
+ const replacement = "/\n\n export type ExchangeId =\n" + Object.keys (classes).map (className => {
1279
+ return " | '" + className + "'"
1280
+ }).join ("\n") + "\n"
1281
+
1282
+ replaceInFile (file, regex, replacement)
1283
+ }
1284
+
1285
+ exportTypeScriptDeclarations (file, classes) {
1286
+
1287
+ this.exportTypeScriptClassNames (file, classes)
1288
+ this.exportTypeScriptExchangeIds (file, classes)
1289
+ }
1290
+
1291
+ // ========================================================================
1292
+
1293
+ transpileErrorHierarchy ({ tsFilename }) {
1294
+
1295
+ const errorHierarchyFilename = './js/base/errorHierarchy.js'
1296
+ const errorHierarchyPath = __dirname + '/.' + errorHierarchyFilename
1297
+ const errorHierarchy = require (errorHierarchyPath)
1298
+
1299
+ let js = fs.readFileSync (errorHierarchyPath, 'utf8')
1300
+
1301
+ js = this.regexAll (js, [
1302
+ [ /module\.exports = [^\;]+\;\n/s, '' ],
1303
+ ]).trim ()
1304
+
1305
+ const message = 'Transpiling error hierachy →'
1306
+ const root = errorHierarchy['BaseError']
1307
+
1308
+ const { python3Body, phpBody } = this.transpileJavaScriptToPythonAndPHP ({ js })
1309
+
1310
+ // a helper to generate a list of exception class declarations
1311
+ // properly derived from corresponding parent classes according
1312
+ // to the error hierarchy
1313
+
1314
+ function intellisense (map, parent, generate, classes) {
1315
+ function* generator(map, parent, generate, classes) {
1316
+ for (const key in map) {
1317
+ yield generate (key, parent, classes)
1318
+ yield* generator (map[key], key, generate, classes)
1319
+ }
1320
+ }
1321
+ return Array.from (generator (map, parent, generate, classes))
1322
+ }
1323
+
1324
+ // Python -------------------------------------------------------------
1325
+
1326
+ function pythonDeclareErrorClass (name, parent, classes) {
1327
+ classes.push (name)
1328
+ return [
1329
+ 'class ' + name + '(' + parent + '):',
1330
+ ' pass',
1331
+ '',
1332
+ '',
1333
+ ].join ('\n');
1334
+ }
1335
+
1336
+ const pythonBaseError = [
1337
+ 'class BaseError(Exception):',
1338
+ ' pass',
1339
+ '',
1340
+ '',
1341
+ ].join ('\n');
1342
+
1343
+ const quote = (s) => "'" + s + "'" // helper to add quotes around class names
1344
+ const pythonExports = [ 'error_hierarchy', 'BaseError' ]
1345
+ const pythonErrors = intellisense (root, 'BaseError', pythonDeclareErrorClass, pythonExports)
1346
+ const pythonAll = '__all__ = [\n ' + pythonExports.map (quote).join (',\n ') + '\n]'
1347
+ const python3BodyIntellisense = python3Body + '\n\n\n' + pythonBaseError + '\n' + pythonErrors.join ('\n') + '\n' + pythonAll + '\n'
1348
+
1349
+ const pythonFilename = './python/ccxt/base/errors.py'
1350
+ if (fs.existsSync (pythonFilename)) {
1351
+ log.bright.cyan (message, pythonFilename.yellow)
1352
+ fs.writeFileSync (pythonFilename, python3BodyIntellisense)
1353
+ }
1354
+
1355
+ // PHP ----------------------------------------------------------------
1356
+
1357
+ function phpMakeErrorClassFile (name, parent) {
1358
+
1359
+ const useClause = "\nuse " + parent + ";\n"
1360
+ const requireClause = "\nrequire_once PATH_TO_CCXT . '" + parent + ".php';\n"
1361
+
1362
+ const phpBody = [
1363
+ '<?php',
1364
+ '',
1365
+ 'namespace ccxt;',
1366
+ (parent === 'Exception') ? useClause : requireClause,
1367
+ 'class ' + name + ' extends ' + parent + ' {};',
1368
+ '',
1369
+ ].join ("\n")
1370
+ const phpFilename = './php/' + name + '.php'
1371
+ log.bright.cyan (message, phpFilename.yellow)
1372
+ fs.writeFileSync (phpFilename, phpBody)
1373
+ return "require_once PATH_TO_CCXT . '" + name + ".php';"
1374
+ }
1375
+
1376
+ const phpFilename ='./ccxt.php'
1377
+
1378
+ if (fs.existsSync (phpFilename)) {
1379
+ const phpErrors = intellisense (errorHierarchy, 'Exception', phpMakeErrorClassFile)
1380
+ const phpBodyIntellisense = phpErrors.join ("\n") + "\n\n"
1381
+ log.bright.cyan (message, phpFilename.yellow)
1382
+ const phpRegex = /require_once PATH_TO_CCXT \. \'BaseError\.php\'\;\n(?:require_once PATH_TO_CCXT[^\n]+\n)+\n/m
1383
+ replaceInFile (phpFilename, phpRegex, phpBodyIntellisense)
1384
+ }
1385
+
1386
+ // TypeScript ---------------------------------------------------------
1387
+
1388
+ function tsDeclareErrorClass (name, parent) {
1389
+ return 'export class ' + name + ' extends ' + parent + ' {}'
1390
+ }
1391
+
1392
+ const tsBaseError = [
1393
+ 'export class BaseError extends Error {',
1394
+ ' constructor(message: string);',
1395
+ '}',
1396
+ ].join ('\n ')
1397
+
1398
+ const tsErrors = intellisense (root, 'BaseError', tsDeclareErrorClass)
1399
+
1400
+ const tsBodyIntellisense = tsBaseError + '\n\n ' + tsErrors.join ('\n ') + '\n\n'
1401
+
1402
+ log.bright.cyan (message, tsFilename.yellow)
1403
+ const regex = /export class BaseError[^}]+\}[\n][\n](?:\s+export class [a-zA-Z]+ extends [a-zA-Z]+ \{\}[\n])+[\n]/m
1404
+ replaceInFile (tsFilename, regex, tsBodyIntellisense)
1405
+ }
1406
+
1407
+ //-----------------------------------------------------------------------------
1408
+
1409
+ transpileDateTimeTests () {
1410
+ const jsFile = './js/test/base/functions/test.datetime.js'
1411
+ const pyFile = './python/ccxt/test/test_exchange_datetime_functions.py'
1412
+ const phpFile = './php/test/test_exchange_datetime_functions.php'
1413
+
1414
+ log.magenta ('Transpiling from', jsFile.yellow)
1415
+
1416
+ let js = fs.readFileSync (jsFile).toString ()
1417
+
1418
+ js = this.regexAll (js, [
1419
+ [ /[^\n]+require[^\n]+\n/g, '' ],
1420
+ [/^\/\*.*\s+/mg, ''],
1421
+ ])
1422
+
1423
+ let { python3Body, python2Body, phpBody, phpAsyncBody } = this.transpileJavaScriptToPythonAndPHP ({ js, removeEmptyLines: false })
1424
+
1425
+ // phpBody = phpBody.replace (/exchange\./g, 'Exchange::')
1426
+
1427
+ const pythonHeader = [
1428
+ "",
1429
+ "import ccxt # noqa: F402",
1430
+ "from ccxt.base.decimal_to_precision import ROUND_UP, ROUND_DOWN # noqa F401",
1431
+ "",
1432
+ "# ----------------------------------------------------------------------------",
1433
+ "",
1434
+ "",
1435
+ ].join ("\n")
1436
+
1437
+ const python = this.getPythonPreamble () + pythonHeader + python2Body
1438
+ const php = this.getPHPPreamble () + phpBody
1439
+
1440
+ log.magenta ('→', pyFile.yellow)
1441
+ log.magenta ('→', phpFile.yellow)
1442
+
1443
+ overwriteFile (pyFile, python)
1444
+ overwriteFile (phpFile, php)
1445
+ }
1446
+
1447
+ //-------------------------------------------------------------------------
1448
+
1449
+ transpilePrecisionTests () {
1450
+
1451
+ const jsFile = './js/test/base/functions/test.number.js'
1452
+ const pyFile = './python/ccxt/test/test_decimal_to_precision.py'
1453
+ const phpFile = './php/test/decimal_to_precision.php'
1454
+
1455
+ log.magenta ('Transpiling from', jsFile.yellow)
1456
+
1457
+ let js = fs.readFileSync (jsFile).toString ()
1458
+
1459
+ js = this.regexAll (js, [
1460
+ [ /\'use strict\';?\s+/g, '' ],
1461
+ [ /[^\n]+require[^\n]+\n/g, '' ],
1462
+ [ /decimalToPrecision/g, 'decimal_to_precision' ],
1463
+ [ /numberToString/g, 'number_to_string' ],
1464
+ ])
1465
+
1466
+ let { python3Body, python2Body, phpBody, phpAsyncBody } = this.transpileJavaScriptToPythonAndPHP ({ js, removeEmptyLines: false })
1467
+
1468
+ const pythonHeader = [
1469
+ "",
1470
+ "from ccxt.base.decimal_to_precision import decimal_to_precision # noqa F401",
1471
+ "from ccxt.base.decimal_to_precision import TRUNCATE # noqa F401",
1472
+ "from ccxt.base.decimal_to_precision import ROUND # noqa F401",
1473
+ "from ccxt.base.decimal_to_precision import DECIMAL_PLACES # noqa F401",
1474
+ "from ccxt.base.decimal_to_precision import SIGNIFICANT_DIGITS # noqa F401",
1475
+ "from ccxt.base.decimal_to_precision import TICK_SIZE # noqa F401",
1476
+ "from ccxt.base.decimal_to_precision import PAD_WITH_ZERO # noqa F401",
1477
+ "from ccxt.base.decimal_to_precision import NO_PADDING # noqa F401",
1478
+ "from ccxt.base.decimal_to_precision import number_to_string # noqa F401",
1479
+ "from ccxt.base.exchange import Exchange # noqa F401",
1480
+ "from ccxt.base.precise import Precise # noqa F401",
1481
+ "",
1482
+ "",
1483
+ ].join ("\n")
1484
+
1485
+ const phpHeader = [
1486
+ "",
1487
+ "include_once (__DIR__.'/fail_on_all_errors.php');",
1488
+ "",
1489
+ "// testDecimalToPrecisionErrorHandling",
1490
+ "//",
1491
+ "// $this->expectException ('ccxt\\\\BaseError');",
1492
+ "// $this->expectExceptionMessageRegExp ('/Negative precision is not yet supported/');",
1493
+ "// Exchange::decimalToPrecision ('123456.789', TRUNCATE, -2, DECIMAL_PLACES);",
1494
+ "//",
1495
+ "// $this->expectException ('ccxt\\\\BaseError');",
1496
+ "// $this->expectExceptionMessageRegExp ('/Invalid number/');",
1497
+ "// Exchange::decimalToPrecision ('foo');",
1498
+ "",
1499
+ "// ----------------------------------------------------------------------------",
1500
+ "",
1501
+ "function decimal_to_precision ($x, $roundingMode = ROUND, $numPrecisionDigits = null, $countingMode = DECIMAL_PLACES, $paddingMode = NO_PADDING) {",
1502
+ " return Exchange::decimal_to_precision ($x, $roundingMode, $numPrecisionDigits, $countingMode, $paddingMode);",
1503
+ "}",
1504
+ "function number_to_string ($x) {",
1505
+ " return Exchange::number_to_string ($x);",
1506
+ "}",
1507
+ "",
1508
+ ].join ("\n")
1509
+
1510
+ const python = this.getPythonPreamble () + pythonHeader + python2Body
1511
+ const php = this.getPHPPreamble () + phpHeader + phpBody
1512
+
1513
+ log.magenta ('→', pyFile.yellow)
1514
+ log.magenta ('→', phpFile.yellow)
1515
+
1516
+ overwriteFile (pyFile, python)
1517
+ overwriteFile (phpFile, php)
1518
+ }
1519
+
1520
+ //-------------------------------------------------------------------------
1521
+
1522
+ transpileCryptoTests () {
1523
+ const jsFile = './js/test/base/functions/test.crypto.js'
1524
+ const pyFile = './python/ccxt/test/test_crypto.py'
1525
+ const phpFile = './php/test/test_crypto.php'
1526
+
1527
+ log.magenta ('Transpiling from', jsFile.yellow)
1528
+ let js = fs.readFileSync (jsFile).toString ()
1529
+
1530
+ js = this.regexAll (js, [
1531
+ [ /\'use strict\';?\s+/g, '' ],
1532
+ [ /[^\n]+require[^\n]+\n/g, '' ],
1533
+ [ /function equals \([\S\s]+?return true\n}\n/g, '' ],
1534
+ ])
1535
+
1536
+ let { python3Body, python2Body, phpBody, phpAsyncBody } = this.transpileJavaScriptToPythonAndPHP ({ js, removeEmptyLines: false })
1537
+
1538
+ const pythonHeader = [
1539
+ "",
1540
+ "import ccxt # noqa: F402",
1541
+ "",
1542
+ "Exchange = ccxt.Exchange",
1543
+ "hash = Exchange.hash",
1544
+ "ecdsa = Exchange.ecdsa",
1545
+ "jwt = Exchange.jwt",
1546
+ "encode = Exchange.encode",
1547
+ "",
1548
+ "",
1549
+ "def equals(a, b):",
1550
+ " return a == b",
1551
+ "",
1552
+ ].join ("\n")
1553
+
1554
+ const phpHeader = [
1555
+ "",
1556
+ "function hash(...$args) {",
1557
+ " return Exchange::hash(...$args);",
1558
+ "}",
1559
+ "",
1560
+ "function encode(...$args) {",
1561
+ " return Exchange::encode(...$args);",
1562
+ "}",
1563
+ "",
1564
+ "function ecdsa(...$args) {",
1565
+ " return Exchange::ecdsa(...$args);",
1566
+ "}",
1567
+ "",
1568
+ "function jwt(...$args) {",
1569
+ " return Exchange::jwt(...$args);",
1570
+ "}",
1571
+ "",
1572
+ "function equals($a, $b) {",
1573
+ " return $a === $b;",
1574
+ "}",
1575
+ ].join ("\n")
1576
+
1577
+ const python = this.getPythonPreamble () + pythonHeader + python2Body
1578
+ const php = this.getPHPPreamble () + phpHeader + phpBody
1579
+
1580
+ log.magenta ('→', pyFile.yellow)
1581
+ log.magenta ('→', phpFile.yellow)
1582
+
1583
+ overwriteFile (pyFile, python)
1584
+ overwriteFile (phpFile, php)
1585
+ }
1586
+
1587
+ // ============================================================================
1588
+
1589
+ transpileExchangeTests () {
1590
+ const tests = [
1591
+ {
1592
+ 'jsFile': './js/test/Exchange/test.market.js',
1593
+ 'pyFile': './python/ccxt/test/test_market.py',
1594
+ 'phpFile': './php/test/test_market.php',
1595
+ },
1596
+ {
1597
+ 'jsFile': './js/test/Exchange/test.trade.js',
1598
+ 'pyFile': './python/ccxt/test/test_trade.py',
1599
+ 'phpFile': './php/test/test_trade.php',
1600
+ },
1601
+ {
1602
+ 'jsFile': './js/test/Exchange/test.order.js',
1603
+ 'pyFile': './python/ccxt/test/test_order.py',
1604
+ 'phpFile': './php/test/test_order.php',
1605
+ },
1606
+ {
1607
+ 'jsFile': './js/test/Exchange/test.position.js',
1608
+ 'pyFile': './python/ccxt/test/test_position.py',
1609
+ 'phpFile': './php/test/test_position.php',
1610
+ },
1611
+ {
1612
+ 'jsFile': './js/test/Exchange/test.transaction.js',
1613
+ 'pyFile': './python/ccxt/test/test_transaction.py',
1614
+ 'phpFile': './php/test/test_transaction.php',
1615
+ },
1616
+ {
1617
+ 'jsFile': './js/test/Exchange/test.ohlcv.js',
1618
+ 'pyFile': './python/ccxt/test/test_ohlcv.py',
1619
+ 'phpFile': './php/test/test_ohlcv.php',
1620
+ },
1621
+ {
1622
+ 'jsFile': './js/test/Exchange/test.leverageTier.js',
1623
+ 'pyFile': './python/ccxt/test/test_leverage_tier.py',
1624
+ 'phpFile': './php/test/test_leverage_tier.php',
1625
+ },
1626
+ ]
1627
+ for (const test of tests) {
1628
+ this.transpileTest (test)
1629
+ }
1630
+ }
1631
+
1632
+ // ============================================================================
1633
+
1634
+ transpileTest (test) {
1635
+ log.magenta ('Transpiling from', test.jsFile.yellow)
1636
+ let js = fs.readFileSync (test.jsFile).toString ()
1637
+
1638
+ js = this.regexAll (js, [
1639
+ [ /\'use strict\';?\s+/g, '' ],
1640
+ [ /[^\n]+require[^\n]+\n/g, '' ],
1641
+ [ /module.exports\s+=\s+[^;]+;/g, '' ],
1642
+ ])
1643
+
1644
+ const pythonHeader = [
1645
+ 'import numbers # noqa: E402',
1646
+ '',
1647
+ '',
1648
+ ].join('\n')
1649
+
1650
+ let { python3Body, python2Body, phpBody } = this.transpileJavaScriptToPythonAndPHP ({ js, removeEmptyLines: false })
1651
+ const python = pythonHeader + python3Body;
1652
+ const php = this.getPHPPreamble (false) + phpBody;
1653
+
1654
+ log.magenta ('→', test.pyFile.yellow)
1655
+ log.magenta ('→', test.phpFile.yellow)
1656
+
1657
+ overwriteFile (test.pyFile, python)
1658
+ overwriteFile (test.phpFile, php)
1659
+ }
1660
+
1661
+ // ============================================================================
1662
+
1663
+ transpileTests () {
1664
+
1665
+ this.transpilePrecisionTests ()
1666
+ this.transpileDateTimeTests ()
1667
+ this.transpileCryptoTests ()
1668
+
1669
+ this.transpileExchangeTests ()
1670
+ }
1671
+
1672
+ // ============================================================================
1673
+
1674
+ transpilePhpBaseClassMethods () {
1675
+ const baseMethods = this.getPHPBaseMethods ()
1676
+ const indent = 4
1677
+ const space = ' '.repeat (indent)
1678
+ const result = [
1679
+ 'public static $camelcase_methods = array(',
1680
+ ]
1681
+ for (const method of baseMethods) {
1682
+ const underscoreCase = unCamelCase (method)
1683
+ if (underscoreCase !== method) {
1684
+ result.push (space.repeat (2) + '\'' + method + '\' => ' + '\'' + underscoreCase + '\',')
1685
+ }
1686
+ }
1687
+ result.push (space + ');')
1688
+ const string = result.join ('\n')
1689
+
1690
+ const phpBaseClass = './php/Exchange.php';
1691
+ const phpBody = fs.readFileSync (phpBaseClass, 'utf8')
1692
+ const regex = /public static \$camelcase_methods = array\([\s\S]+?\);/g
1693
+ const bodyArray = phpBody.split (regex)
1694
+
1695
+ const newBody = bodyArray[0] + string + bodyArray[1]
1696
+
1697
+ log.magenta ('Transpiling from ', phpBaseClass.yellow, '→', phpBaseClass.yellow)
1698
+ overwriteFile (phpBaseClass, newBody)
1699
+ }
1700
+
1701
+ // ============================================================================
1702
+
1703
+ transpileEverything (force = false) {
1704
+
1705
+ // default pattern is '.js'
1706
+ const [ /* node */, /* script */, pattern ] = process.argv.filter (x => !x.startsWith ('--'))
1707
+ , python2Folder = './python/ccxt/'
1708
+ , python3Folder = './python/ccxt/async_support/'
1709
+ , phpFolder = './php/'
1710
+ , phpAsyncFolder = './php/async/'
1711
+ , options = { python2Folder, python3Folder, phpFolder, phpAsyncFolder }
1712
+
1713
+ createFolderRecursively (python2Folder)
1714
+ createFolderRecursively (python3Folder)
1715
+ createFolderRecursively (phpFolder)
1716
+ createFolderRecursively (phpAsyncFolder)
1717
+
1718
+ //*
1719
+
1720
+ const classes = this.transpileDerivedExchangeFiles ('./js/', options, pattern, force)
1721
+
1722
+ if (classes === null) {
1723
+ log.bright.yellow ('0 files transpiled.')
1724
+ return;
1725
+ }
1726
+
1727
+ // HINT: if we're going to support specific class definitions
1728
+ // this process won't work anymore as it will override the definitions
1729
+ this.exportTypeScriptDeclarations (tsFilename, classes)
1730
+
1731
+ //*/
1732
+
1733
+ this.transpileErrorHierarchy ({ tsFilename })
1734
+
1735
+ this.transpileTests ()
1736
+
1737
+ this.transpilePythonAsyncToSync ()
1738
+
1739
+ this.transpilePhpAsyncToSync ()
1740
+
1741
+ this.transpilePhpBaseClassMethods ()
1742
+
1743
+ log.bright.green ('Transpiled successfully.')
1744
+ }
1745
+ }
1746
+
1747
+ // ============================================================================
1748
+ // main entry point
1749
+
1750
+ if (require.main === module) { // called directly like `node module`
1751
+
1752
+ const transpiler = new Transpiler ()
1753
+ const test = process.argv.includes ('--test') || process.argv.includes ('--tests')
1754
+ const errors = process.argv.includes ('--error') || process.argv.includes ('--errors')
1755
+ const force = process.argv.includes ('--force')
1756
+ log.bright.green ({ force })
1757
+ if (test) {
1758
+ transpiler.transpileTests ()
1759
+ } else if (errors) {
1760
+ transpiler.transpileErrorHierarchy ({ tsFilename })
1761
+ } else {
1762
+ transpiler.transpileEverything (force)
1763
+ }
1764
+
1765
+ } else { // if required as a module
1766
+
1767
+ // do nothing
1768
+ }
1769
+
1770
+ // ============================================================================
1771
+
1772
+ module.exports = Transpiler