naider 1.11.1 → 1.13.0

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.
@@ -141,7 +141,18 @@ export class PythonGenerator {
141
141
  case 'WsDecl': return this.visitWs(node);
142
142
  case 'SessionDecl': return this.visitSession('app', node);
143
143
  case 'CacheDecl': return this.visitCache('app', node);
144
+ case 'EveryDecl': return this.visitEvery(node);
144
145
  case 'PromptDecl': return this.visitPrompt(node);
146
+ case 'Page': return this.visitPage(node);
147
+ case 'CliApp': return this.visitCli(node);
148
+ case 'MailConfig': return this.visitMail(node);
149
+ case 'DesktopApp': return this.visitDesktop(node);
150
+ case 'Screen': return this.visitScreen(node);
151
+ case 'OauthDecl': return this.visitOauth(node);
152
+ case 'PayDecl': return this.visitPay(node);
153
+ case 'StorageDecl': return this.visitStorage(node);
154
+ case 'PdfDecl': return this.visitPdf(node);
155
+ case 'I18nDecl': return this.visitI18n(node);
145
156
  default:
146
157
  this.emit(`# unknown: ${node.type}`);
147
158
  }
@@ -447,6 +458,8 @@ export class PythonGenerator {
447
458
  this.visitSession(node.name, child);
448
459
  } else if (child.type === 'CacheDecl') {
449
460
  this.visitCache(node.name, child);
461
+ } else if (child.type === 'GraphqlDecl') {
462
+ this.visitGraphql(node.name, child);
450
463
  } else {
451
464
  this.visitStatement(child);
452
465
  }
@@ -1427,11 +1440,24 @@ export class PythonGenerator {
1427
1440
  this.emit(`# cache: ${path} for ${ttl}`);
1428
1441
  }
1429
1442
 
1430
- // ===== Discord Bot (discord.py) =====
1443
+ // ===== Bot (multi-platform) =====
1431
1444
 
1432
1445
  visitBot(node) {
1433
- const tokenExpr = node.token ? this.expr(node.token) : "os.environ.get('DISCORD_TOKEN')";
1446
+ const tokenExpr = node.token ? this.expr(node.token) : "os.environ.get('BOT_TOKEN')";
1447
+ const botType = this.rawBotType(node.botType);
1434
1448
 
1449
+ if (botType === 'slack') return this.visitSlackBot(node, tokenExpr);
1450
+ if (botType === 'telegram') return this.visitTelegramBot(node, tokenExpr);
1451
+ if (botType === 'line') return this.visitLineBot(node, tokenExpr);
1452
+ return this.visitDiscordBot(node, tokenExpr);
1453
+ }
1454
+
1455
+ rawBotType(bt) {
1456
+ if (!bt) return 'discord';
1457
+ return bt.raw || bt.parts?.map(p => p.value).join('') || 'discord';
1458
+ }
1459
+
1460
+ visitDiscordBot(node, tokenExpr) {
1435
1461
  this.addImport('discord');
1436
1462
  this.addFromImport('discord.ext', 'commands');
1437
1463
  this.emitRaw('');
@@ -1496,6 +1522,424 @@ export class PythonGenerator {
1496
1522
  this.emitRaw('');
1497
1523
  }
1498
1524
 
1525
+ visitSlackBot(node, tokenExpr) {
1526
+ this.addFromImport('slack_bolt', 'App');
1527
+ this.emitRaw('');
1528
+ this.emit(`${node.name} = App(token=${tokenExpr}, signing_secret=os.environ.get('SLACK_SIGNING_SECRET'))`);
1529
+ this.usesOs = true;
1530
+ this.emitRaw('');
1531
+
1532
+ for (const handler of node.handlers) {
1533
+ if (handler.type === 'BotEvent') {
1534
+ const evtName = handler.event.raw || handler.event.parts?.map(p => p.value).join('');
1535
+ if (evtName === 'message') {
1536
+ this.emit(`@${node.name}.message("")`);
1537
+ this.emit(`def handle_message(message, say):`);
1538
+ } else {
1539
+ this.emit(`@${node.name}.event("${evtName}")`);
1540
+ this.emit(`def handle_${evtName}(event):`);
1541
+ }
1542
+ this.indent++;
1543
+ if (handler.body.length === 0) { this.emit('pass'); } else {
1544
+ for (const stmt of handler.body) this.visitStatement(stmt);
1545
+ }
1546
+ this.indent--;
1547
+ this.emitRaw('');
1548
+ } else if (handler.type === 'BotSlashCmd') {
1549
+ const cmdName = handler.name.raw || handler.name.parts?.map(p => p.value).join('');
1550
+ this.emit(`@${node.name}.command("/${cmdName}")`);
1551
+ this.emit(`def handle_${cmdName}(ack, respond, command):`);
1552
+ this.indent++;
1553
+ this.emit('ack()');
1554
+ for (const stmt of handler.body) this.visitStatement(stmt);
1555
+ this.indent--;
1556
+ this.emitRaw('');
1557
+ }
1558
+ }
1559
+
1560
+ this.emit(`if __name__ == '__main__':`);
1561
+ this.indent++;
1562
+ this.emit(`${node.name}.start(port=3000)`);
1563
+ this.indent--;
1564
+ this.emitRaw('');
1565
+ }
1566
+
1567
+ visitTelegramBot(node, tokenExpr) {
1568
+ this.addFromImport('telegram.ext', 'ApplicationBuilder');
1569
+ this.addFromImport('telegram.ext', 'MessageHandler');
1570
+ this.addFromImport('telegram.ext', 'CommandHandler');
1571
+ this.addFromImport('telegram.ext', 'filters');
1572
+ this.emitRaw('');
1573
+ this.emit(`${node.name} = ApplicationBuilder().token(${tokenExpr}).build()`);
1574
+ this.emitRaw('');
1575
+
1576
+ for (const handler of node.handlers) {
1577
+ if (handler.type === 'BotEvent') {
1578
+ const evtName = handler.event.raw || handler.event.parts?.map(p => p.value).join('');
1579
+ const params = handler.params.length > 0 ? handler.params.join(', ') : 'update, context';
1580
+ this.emit(`async def handle_${evtName}(${params}):`);
1581
+ this.indent++;
1582
+ if (handler.body.length === 0) { this.emit('pass'); } else {
1583
+ for (const stmt of handler.body) this.visitStatement(stmt);
1584
+ }
1585
+ this.indent--;
1586
+ this.emit(`${node.name}.add_handler(MessageHandler(filters.TEXT, handle_${evtName}))`);
1587
+ this.emitRaw('');
1588
+ } else if (handler.type === 'BotSlashCmd') {
1589
+ const cmdName = handler.name.raw || handler.name.parts?.map(p => p.value).join('');
1590
+ this.emit(`async def cmd_${cmdName}(update, context):`);
1591
+ this.indent++;
1592
+ if (handler.body.length === 0) { this.emit('pass'); } else {
1593
+ for (const stmt of handler.body) this.visitStatement(stmt);
1594
+ }
1595
+ this.indent--;
1596
+ this.emit(`${node.name}.add_handler(CommandHandler("${cmdName}", cmd_${cmdName}))`);
1597
+ this.emitRaw('');
1598
+ }
1599
+ }
1600
+
1601
+ this.emit(`${node.name}.run_polling()`);
1602
+ this.emitRaw('');
1603
+ }
1604
+
1605
+ visitLineBot(node, tokenExpr) {
1606
+ this.addFromImport('linebot', 'LineBotApi');
1607
+ this.addFromImport('linebot', 'WebhookHandler');
1608
+ this.addFromImport('flask', 'Flask');
1609
+ this.addFromImport('flask', 'request');
1610
+ this.addFromImport('flask', 'abort');
1611
+ this.emitRaw('');
1612
+ this.emit(`${node.name} = LineBotApi(${tokenExpr})`);
1613
+ this.emit(`__handler = WebhookHandler(os.environ.get('LINE_CHANNEL_SECRET'))`);
1614
+ this.usesOs = true;
1615
+ this.emit(`__app = Flask(__name__)`);
1616
+ this.emitRaw('');
1617
+
1618
+ this.emit(`@__app.route('/webhook', methods=['POST'])`);
1619
+ this.emit(`def webhook():`);
1620
+ this.indent++;
1621
+ this.emit(`signature = request.headers['X-Line-Signature']`);
1622
+ this.emit(`body = request.get_data(as_text=True)`);
1623
+ this.emit(`__handler.handle(body, signature)`);
1624
+ this.emit(`return 'OK'`);
1625
+ this.indent--;
1626
+ this.emitRaw('');
1627
+
1628
+ for (const handler of node.handlers) {
1629
+ if (handler.type === 'BotEvent') {
1630
+ const evtName = handler.event.raw || handler.event.parts?.map(p => p.value).join('');
1631
+ if (evtName === 'message') {
1632
+ this.emit(`@__handler.add(MessageEvent, message=TextMessage)`);
1633
+ this.emit(`def handle_message(event):`);
1634
+ } else {
1635
+ this.emit(`@__handler.add(${evtName})`);
1636
+ this.emit(`def handle_${evtName}(event):`);
1637
+ }
1638
+ this.indent++;
1639
+ if (handler.body.length === 0) { this.emit('pass'); } else {
1640
+ for (const stmt of handler.body) this.visitStatement(stmt);
1641
+ }
1642
+ this.indent--;
1643
+ this.emitRaw('');
1644
+ }
1645
+ }
1646
+
1647
+ this.emit(`if __name__ == '__main__':`);
1648
+ this.indent++;
1649
+ this.emit(`__app.run(port=3000)`);
1650
+ this.indent--;
1651
+ this.emitRaw('');
1652
+ }
1653
+
1654
+ // ===== Every (cron/scheduler) =====
1655
+
1656
+ visitEvery(node) {
1657
+ const raw = node.interval?.value?.raw || node.interval?.value?.parts?.map(p => p.value).join('') || '';
1658
+ const isCron = /^[\d*\/,-]+\s+[\d*\/,-]+\s+[\d*\/,-]+\s+[\d*\/,-]+\s+[\d*\/,-]+/.test(raw.trim());
1659
+
1660
+ if (isCron) {
1661
+ this.addFromImport('apscheduler.schedulers.blocking', 'BlockingScheduler');
1662
+ this.addFromImport('apscheduler.triggers.cron', 'CronTrigger');
1663
+ this.emitRaw('');
1664
+ const parts = raw.trim().split(/\s+/);
1665
+ this.emit(`__scheduler = BlockingScheduler()`);
1666
+ this.emit(`def __every_job():`);
1667
+ this.indent++;
1668
+ if (node.body.length === 0) { this.emit('pass'); } else {
1669
+ for (const stmt of node.body) this.visitStatement(stmt);
1670
+ }
1671
+ this.indent--;
1672
+ this.emit(`__scheduler.add_job(__every_job, CronTrigger(minute='${parts[0]}', hour='${parts[1]}', day='${parts[2]}', month='${parts[3]}', day_of_week='${parts[4]}'))`);
1673
+ this.emit(`__scheduler.start()`);
1674
+ } else {
1675
+ this.addImport('schedule');
1676
+ this.addImport('time');
1677
+ this.emitRaw('');
1678
+ const interval = this.expr(node.interval);
1679
+ this.emit(`def __every_job():`);
1680
+ this.indent++;
1681
+ if (node.body.length === 0) { this.emit('pass'); } else {
1682
+ for (const stmt of node.body) this.visitStatement(stmt);
1683
+ }
1684
+ this.indent--;
1685
+ this.emit(`schedule.every(${interval}).do(__every_job)`);
1686
+ this.emit(`while True:`);
1687
+ this.indent++;
1688
+ this.emit(`schedule.run_pending()`);
1689
+ this.emit(`time.sleep(1)`);
1690
+ this.indent--;
1691
+ }
1692
+ this.emitRaw('');
1693
+ }
1694
+
1695
+ // ===== GraphQL =====
1696
+
1697
+ visitGraphql(appName, node) {
1698
+ const path = this.generateString(node.path);
1699
+ this.addFromImport('flask_graphql', 'GraphQLView');
1700
+ this.addFromImport('graphene', '*');
1701
+ this.emitRaw('');
1702
+
1703
+ const schemaNames = [...(this.schemas || new Map()).keys()];
1704
+ if (schemaNames.length > 0) {
1705
+ for (const name of schemaNames) {
1706
+ const schema = this.schemas.get(name);
1707
+ this.emit(`class ${name}Type(ObjectType):`);
1708
+ this.indent++;
1709
+ for (const f of schema.fields) {
1710
+ const gqlType = this.toGraphqlType(f.type);
1711
+ this.emit(`${f.name} = ${gqlType}()`);
1712
+ }
1713
+ this.indent--;
1714
+ this.emitRaw('');
1715
+ }
1716
+ this.emit(`class Query(ObjectType):`);
1717
+ this.indent++;
1718
+ for (const name of schemaNames) {
1719
+ this.emit(`${name.toLowerCase()}s = List(${name}Type)`);
1720
+ this.emit(`def resolve_${name.toLowerCase()}s(self, info):`);
1721
+ this.indent++;
1722
+ this.emit(`return []`);
1723
+ this.indent--;
1724
+ }
1725
+ this.indent--;
1726
+ } else {
1727
+ this.emit(`class Query(ObjectType):`);
1728
+ this.indent++;
1729
+ this.emit(`hello = String()`);
1730
+ this.emit(`def resolve_hello(self, info):`);
1731
+ this.indent++;
1732
+ this.emit(`return 'Hello from NAIDE GraphQL'`);
1733
+ this.indent--;
1734
+ this.indent--;
1735
+ }
1736
+
1737
+ this.emitRaw('');
1738
+ this.emit(`__gql_schema = Schema(query=Query)`);
1739
+ const rawPath = this.rawString(node.path);
1740
+ this.emit(`${appName}.add_url_rule(${JSON.stringify(rawPath)}, view_func=GraphQLView.as_view('graphql', schema=__gql_schema, graphiql=True))`);
1741
+ this.emitRaw('');
1742
+ }
1743
+
1744
+ toGraphqlType(naideType) {
1745
+ const map = { 'str': 'graphene.String', 'int': 'graphene.Int', 'num': 'graphene.Float', 'bool': 'graphene.Boolean', 'auto': 'graphene.ID', 'timestamp': 'graphene.String', 'enum': 'graphene.String' };
1746
+ return map[naideType] || 'graphene.String';
1747
+ }
1748
+
1749
+ // ===== Page (HTML generation) =====
1750
+
1751
+ visitPage(node) {
1752
+ const filename = this.rawString(node.filename);
1753
+ const headParts = [];
1754
+ const bodyParts = [];
1755
+ let pageTitle = 'NAIDE Page';
1756
+
1757
+ for (const el of node.elements) {
1758
+ this.classifyPageElement(el, headParts, bodyParts, (t) => { pageTitle = t; });
1759
+ }
1760
+
1761
+ this.emit(`with open(${JSON.stringify(filename)}, 'w') as __f:`);
1762
+ this.indent++;
1763
+ this.emit(`__f.write("""<!DOCTYPE html>`);
1764
+ this.emit(`<html lang="en">`);
1765
+ this.emit(`<head>`);
1766
+ this.emit(`<meta charset="UTF-8">`);
1767
+ this.emit(`<meta name="viewport" content="width=device-width, initial-scale=1.0">`);
1768
+ this.emit(`<title>${pageTitle}</title>`);
1769
+ for (const h of headParts) this.emit(h);
1770
+ this.emit(`</head>`);
1771
+ this.emit(`<body>`);
1772
+ for (const b of bodyParts) this.emit(b);
1773
+ this.emit(`</body>`);
1774
+ this.emit(`</html>""")`);
1775
+ this.indent--;
1776
+ this.emit(`print(f"Generated: ${filename}")`);
1777
+ this.emitRaw('');
1778
+ }
1779
+
1780
+ classifyPageElement(el, head, body, setTitle) {
1781
+ const tag = el.tag;
1782
+ const arg0 = el.args[0] ? this.rawString(el.args[0]) : '';
1783
+ const arg1 = el.args[1] ? this.rawString(el.args[1]) : '';
1784
+ if (tag === 'title') { setTitle(arg0); return; }
1785
+ if (tag === 'style' || tag === 'link') { head.push(`<link rel="stylesheet" href="${arg0}">`); return; }
1786
+ if (tag === 'meta') { head.push(`<meta name="${arg0}" content="${arg1}">`); return; }
1787
+ if (tag === 'script') { body.push(`<script src="${arg0}"></script>`); return; }
1788
+ if (tag === 'img') { body.push(`<img src="${arg0}" alt="${arg1}">`); return; }
1789
+ if (tag === 'a') { body.push(`<a href="${arg0}">${arg1}</a>`); return; }
1790
+ if (tag === 'input') { body.push(`<input type="${arg0}" name="${arg1}">`); return; }
1791
+ if (el.children && el.children.length > 0) {
1792
+ const cls = arg0 ? ` class="${arg0}"` : '';
1793
+ body.push(`<${tag}${cls}>`);
1794
+ for (const child of el.children) this.classifyPageElement(child, head, body, setTitle);
1795
+ body.push(`</${tag}>`);
1796
+ } else {
1797
+ body.push(`<${tag}>${arg0}</${tag}>`);
1798
+ }
1799
+ }
1800
+
1801
+ // ===== CLI App =====
1802
+
1803
+ visitCli(node) {
1804
+ this.addImport('argparse');
1805
+ this.emitRaw('');
1806
+ const desc = node.description ? this.rawString(node.description) : node.name;
1807
+ this.emit(`__parser = argparse.ArgumentParser(description=${JSON.stringify(desc)})`);
1808
+
1809
+ for (const arg of node.args) {
1810
+ const argName = this.rawString(arg.name);
1811
+ const pyType = arg.type === 'int' ? 'int' : arg.type === 'num' ? 'float' : 'str';
1812
+ const desc = arg.description ? `, help=${JSON.stringify(this.rawString(arg.description))}` : '';
1813
+ this.emit(`__parser.add_argument('--${argName}', type=${pyType}${desc})`);
1814
+ }
1815
+ for (const flag of node.flags) {
1816
+ const s = this.rawString(flag.short);
1817
+ const l = this.rawString(flag.long);
1818
+ const desc = flag.description ? `, help=${JSON.stringify(this.rawString(flag.description))}` : '';
1819
+ this.emit(`__parser.add_argument('-${s}', '--${l}', action='store_true'${desc})`);
1820
+ }
1821
+
1822
+ this.emit(`args = __parser.parse_args()`);
1823
+ this.emitRaw('');
1824
+
1825
+ if (node.run) {
1826
+ const runParam = node.run.params.length > 0 ? node.run.params[0] : 'args';
1827
+ if (runParam !== 'args') {
1828
+ this.emit(`${runParam} = args`);
1829
+ }
1830
+ for (const stmt of node.run.body) this.visitStatement(stmt);
1831
+ }
1832
+ this.emitRaw('');
1833
+ }
1834
+
1835
+ // ===== Mail =====
1836
+
1837
+ visitMail(node) {
1838
+ this.addImport('smtplib');
1839
+ this.addFromImport('email.mime.text', 'MIMEText');
1840
+ this.addFromImport('email.mime.multipart', 'MIMEMultipart');
1841
+ this.usesOs = true;
1842
+ this.emitRaw('');
1843
+
1844
+ const host = this.generateString(node.host);
1845
+ const port = this.expr(node.port);
1846
+ const user = node.user ? this.expr(node.user) : "os.environ.get('MAIL_USER')";
1847
+ const pass = node.pass ? this.expr(node.pass) : "os.environ.get('MAIL_PASS')";
1848
+
1849
+ this.emit(`class Mail:`);
1850
+ this.indent++;
1851
+ this.emit(`def __init__(self, host, port, user, password):`);
1852
+ this.indent++;
1853
+ this.emit(`self.host = host`);
1854
+ this.emit(`self.port = port`);
1855
+ this.emit(`self.user = user`);
1856
+ this.emit(`self.password = password`);
1857
+ this.indent--;
1858
+ this.emitRaw('');
1859
+ this.emit(`def send(self, to, subject, body):`);
1860
+ this.indent++;
1861
+ this.emit(`msg = MIMEText(body)`);
1862
+ this.emit(`msg['Subject'] = subject`);
1863
+ this.emit(`msg['From'] = self.user`);
1864
+ this.emit(`msg['To'] = to`);
1865
+ this.emit(`with smtplib.SMTP(self.host, self.port) as server:`);
1866
+ this.indent++;
1867
+ this.emit(`server.starttls()`);
1868
+ this.emit(`server.login(self.user, self.password)`);
1869
+ this.emit(`server.send_message(msg)`);
1870
+ this.indent--;
1871
+ this.indent--;
1872
+ this.indent--;
1873
+ this.emitRaw('');
1874
+ this.emit(`mail = Mail(${host}, ${port}, ${user}, ${pass})`);
1875
+ this.emitRaw('');
1876
+ }
1877
+
1878
+ // ===== Desktop (pywebview) =====
1879
+
1880
+ visitDesktop(node) {
1881
+ this.addImport('webview');
1882
+ this.emitRaw('');
1883
+ const title = node.title ? this.rawString(node.title) : node.name;
1884
+ const width = node.width ? this.expr(node.width) : '800';
1885
+ const height = node.height ? this.expr(node.height) : '600';
1886
+ const load = node.load ? this.expr(node.load) : '"index.html"';
1887
+ this.emit(`webview.create_window(${JSON.stringify(title)}, ${load}, width=${width}, height=${height})`);
1888
+ this.emit(`webview.start()`);
1889
+ this.emitRaw('');
1890
+ }
1891
+
1892
+ // ===== Screen (Kivy) =====
1893
+
1894
+ visitScreen(node) {
1895
+ this.addFromImport('kivy.app', 'App');
1896
+ this.addFromImport('kivy.uix.boxlayout', 'BoxLayout');
1897
+ this.addFromImport('kivy.uix.label', 'Label');
1898
+ this.addFromImport('kivy.uix.button', 'Button');
1899
+ this.addFromImport('kivy.uix.textinput', 'TextInput');
1900
+ this.emitRaw('');
1901
+
1902
+ this.emit(`class ${node.name}Screen(BoxLayout):`);
1903
+ this.indent++;
1904
+ this.emit(`def __init__(self, **kwargs):`);
1905
+ this.indent++;
1906
+ this.emit(`super().__init__(**kwargs)`);
1907
+ this.emit(`self.orientation = 'vertical'`);
1908
+ this.emit(`self.padding = 16`);
1909
+
1910
+ for (const el of node.elements) {
1911
+ const tag = el.tag;
1912
+ const arg0 = el.args[0] ? this.rawString(el.args[0]) : '';
1913
+ if (tag === 'text') {
1914
+ this.emit(`self.add_widget(Label(text=${JSON.stringify(arg0)}))`);
1915
+ } else if (tag === 'button') {
1916
+ this.emit(`self.add_widget(Button(text=${JSON.stringify(arg0)}))`);
1917
+ } else if (tag === 'input') {
1918
+ this.emit(`self.add_widget(TextInput(hint_text=${JSON.stringify(arg0)}))`);
1919
+ } else {
1920
+ this.emit(`self.add_widget(Label(text=${JSON.stringify(arg0)}))`);
1921
+ }
1922
+ }
1923
+
1924
+ this.indent--;
1925
+ this.indent--;
1926
+ this.emitRaw('');
1927
+
1928
+ this.emit(`class ${node.name}App(App):`);
1929
+ this.indent++;
1930
+ this.emit(`def build(self):`);
1931
+ this.indent++;
1932
+ this.emit(`return ${node.name}Screen()`);
1933
+ this.indent--;
1934
+ this.indent--;
1935
+ this.emitRaw('');
1936
+ this.emit(`if __name__ == '__main__':`);
1937
+ this.indent++;
1938
+ this.emit(`${node.name}App().run()`);
1939
+ this.indent--;
1940
+ this.emitRaw('');
1941
+ }
1942
+
1499
1943
  // ===== Prompt template =====
1500
1944
 
1501
1945
  visitPrompt(node) {
@@ -1516,4 +1960,190 @@ export class PythonGenerator {
1516
1960
  this.indent--;
1517
1961
  this.emitRaw('');
1518
1962
  }
1963
+
1964
+ // ===== OAuth =====
1965
+ visitOauth(node) {
1966
+ const provider = this.rawString(node.provider);
1967
+ const clientId = this.expr(node.clientId);
1968
+ const clientSecret = this.expr(node.clientSecret);
1969
+ const callback = node.callback ? this.rawString(node.callback) : '/auth/callback';
1970
+ const scope = node.scope ? this.rawString(node.scope) : 'email profile';
1971
+
1972
+ if (provider === 'google') {
1973
+ this.addFromImport('authlib.integrations.flask_client', 'OAuth');
1974
+ } else if (provider === 'github') {
1975
+ this.addFromImport('authlib.integrations.flask_client', 'OAuth');
1976
+ }
1977
+ this.emitRaw('');
1978
+ this.emit(`oauth = OAuth()`);
1979
+ this.emit(`oauth.register(`);
1980
+ this.indent++;
1981
+ this.emit(`name=${JSON.stringify(provider)},`);
1982
+ this.emit(`client_id=${clientId},`);
1983
+ this.emit(`client_secret=${clientSecret},`);
1984
+ if (provider === 'google') {
1985
+ this.emit(`server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',`);
1986
+ this.emit(`client_kwargs={'scope': ${JSON.stringify(scope)}},`);
1987
+ } else if (provider === 'github') {
1988
+ this.emit(`access_token_url='https://github.com/login/oauth/access_token',`);
1989
+ this.emit(`authorize_url='https://github.com/login/oauth/authorize',`);
1990
+ this.emit(`client_kwargs={'scope': ${JSON.stringify(scope)}},`);
1991
+ }
1992
+ this.indent--;
1993
+ this.emit(`)`);
1994
+ this.emitRaw('');
1995
+ }
1996
+
1997
+ // ===== Pay =====
1998
+ visitPay(node) {
1999
+ const provider = this.rawString(node.provider);
2000
+ const secretKey = this.expr(node.secretKey);
2001
+
2002
+ if (provider === 'stripe') {
2003
+ this.addImport('stripe');
2004
+ this.emitRaw('');
2005
+ this.emit(`stripe.api_key = ${secretKey}`);
2006
+ this.emitRaw('');
2007
+ this.emit(`class Pay:`);
2008
+ this.indent++;
2009
+ this.emit(`@staticmethod`);
2010
+ this.emit(`def checkout(items, success_url=None, cancel_url=None):`);
2011
+ this.indent++;
2012
+ this.emit(`line_items = [{'price_data': {'currency': 'usd', 'product_data': {'name': i['name']}, 'unit_amount': i['price']}, 'quantity': i.get('qty', 1)} for i in items]`);
2013
+ this.emit(`return stripe.checkout.Session.create(line_items=line_items, mode='payment', success_url=success_url or 'http://localhost:3000/success', cancel_url=cancel_url or 'http://localhost:3000/cancel')`);
2014
+ this.indent--;
2015
+ this.indent--;
2016
+ this.emit(`pay = Pay()`);
2017
+ } else {
2018
+ this.emit(`# ${provider} payment integration`);
2019
+ this.emit(`pay = None`);
2020
+ }
2021
+ this.emitRaw('');
2022
+ }
2023
+
2024
+ // ===== Storage =====
2025
+ visitStorage(node) {
2026
+ const provider = this.rawString(node.provider);
2027
+ const bucket = this.expr(node.bucket);
2028
+ const accessKey = this.expr(node.accessKey);
2029
+ const secretKey = this.expr(node.secretKey);
2030
+ const region = node.region ? this.rawString(node.region) : 'us-east-1';
2031
+
2032
+ if (provider === 's3') {
2033
+ this.addImport('boto3');
2034
+ this.emitRaw('');
2035
+ this.emit(`__s3 = boto3.client('s3', region_name=${JSON.stringify(region)}, aws_access_key_id=${accessKey}, aws_secret_access_key=${secretKey})`);
2036
+ this.emitRaw('');
2037
+ this.emit(`class Storage:`);
2038
+ this.indent++;
2039
+ this.emit(`@staticmethod`);
2040
+ this.emit(`def upload(key, body):`);
2041
+ this.indent++;
2042
+ this.emit(`__s3.put_object(Bucket=${bucket}, Key=key, Body=body)`);
2043
+ this.indent--;
2044
+ this.emit(`@staticmethod`);
2045
+ this.emit(`def download(key):`);
2046
+ this.indent++;
2047
+ this.emit(`return __s3.get_object(Bucket=${bucket}, Key=key)['Body'].read()`);
2048
+ this.indent--;
2049
+ this.emit(`@staticmethod`);
2050
+ this.emit(`def remove(key):`);
2051
+ this.indent++;
2052
+ this.emit(`__s3.delete_object(Bucket=${bucket}, Key=key)`);
2053
+ this.indent--;
2054
+ this.indent--;
2055
+ this.emit(`storage = Storage()`);
2056
+ } else if (provider === 'gcs') {
2057
+ this.addFromImport('google.cloud', 'storage as gcs_storage');
2058
+ this.emit(`__gcs = gcs_storage.Client()`);
2059
+ this.emit(`__bucket = __gcs.bucket(${bucket})`);
2060
+ this.emit(`class Storage:`);
2061
+ this.indent++;
2062
+ this.emit(`@staticmethod`);
2063
+ this.emit(`def upload(key, body): __bucket.blob(key).upload_from_string(body)`);
2064
+ this.emit(`@staticmethod`);
2065
+ this.emit(`def download(key): return __bucket.blob(key).download_as_bytes()`);
2066
+ this.emit(`@staticmethod`);
2067
+ this.emit(`def remove(key): __bucket.blob(key).delete()`);
2068
+ this.indent--;
2069
+ this.emit(`storage = Storage()`);
2070
+ }
2071
+ this.emitRaw('');
2072
+ }
2073
+
2074
+ // ===== PDF =====
2075
+ visitPdf(node) {
2076
+ const filename = this.rawString(node.filename);
2077
+ this.addFromImport('fpdf', 'FPDF');
2078
+ this.emitRaw('');
2079
+ this.emit(`__pdf = FPDF()`);
2080
+ this.emit(`__pdf.add_page()`);
2081
+ this.emit(`__pdf.set_auto_page_break(auto=True, margin=15)`);
2082
+
2083
+ for (const el of node.elements) {
2084
+ const tag = el.tag;
2085
+ const arg0 = el.args[0] ? this.rawString(el.args[0]) : '';
2086
+ if (tag === 'title') {
2087
+ this.emit(`__pdf.set_font('Helvetica', 'B', 24)`);
2088
+ this.emit(`__pdf.cell(0, 15, ${JSON.stringify(arg0)}, ln=True)`);
2089
+ } else if (tag === 'h1' || tag === 'h2' || tag === 'heading') {
2090
+ const size = tag === 'h1' ? 20 : 16;
2091
+ this.emit(`__pdf.set_font('Helvetica', 'B', ${size})`);
2092
+ this.emit(`__pdf.cell(0, 12, ${JSON.stringify(arg0)}, ln=True)`);
2093
+ } else if (tag === 'text' || tag === 'p') {
2094
+ this.emit(`__pdf.set_font('Helvetica', '', 12)`);
2095
+ this.emit(`__pdf.multi_cell(0, 8, ${JSON.stringify(arg0)})`);
2096
+ } else if (tag === 'image') {
2097
+ this.emit(`__pdf.image(${JSON.stringify(arg0)}, w=100)`);
2098
+ } else if (tag === 'line') {
2099
+ this.emit(`__pdf.line(10, __pdf.get_y(), 200, __pdf.get_y())`);
2100
+ } else {
2101
+ this.emit(`__pdf.set_font('Helvetica', '', 12)`);
2102
+ this.emit(`__pdf.cell(0, 8, ${JSON.stringify(arg0)}, ln=True)`);
2103
+ }
2104
+ }
2105
+
2106
+ this.emit(`__pdf.output(${JSON.stringify(filename)})`);
2107
+ this.emit(`print(f"Generated: ${filename}")`);
2108
+ this.emitRaw('');
2109
+ }
2110
+
2111
+ // ===== i18n =====
2112
+ visitI18n(node) {
2113
+ const dir = this.rawString(node.dir);
2114
+ const defaultLang = node.defaultLang ? this.rawString(node.defaultLang) : 'en';
2115
+
2116
+ this.addImport('json');
2117
+ this.addImport('os');
2118
+ this.emitRaw('');
2119
+ this.emit(`__i18n_data = {}`);
2120
+ for (const lang of node.langs) {
2121
+ const code = this.rawString(lang.code);
2122
+ const file = this.rawString(lang.file);
2123
+ this.emit(`with open(os.path.join(${JSON.stringify(dir)}, ${JSON.stringify(file)})) as f:`);
2124
+ this.indent++;
2125
+ this.emit(`__i18n_data[${JSON.stringify(code)}] = json.load(f)`);
2126
+ this.indent--;
2127
+ }
2128
+ this.emit(`__i18n_lang = ${JSON.stringify(defaultLang)}`);
2129
+ this.emitRaw('');
2130
+ this.emit(`class I18n:`);
2131
+ this.indent++;
2132
+ this.emit(`@staticmethod`);
2133
+ this.emit(`def t(key, **params):`);
2134
+ this.indent++;
2135
+ this.emit(`data = __i18n_data.get(__i18n_lang, {})`);
2136
+ this.emit(`for k in key.split('.'): data = data.get(k, key) if isinstance(data, dict) else key`);
2137
+ this.emit(`text = str(data)`);
2138
+ this.emit(`for k, v in params.items(): text = text.replace('{' + k + '}', str(v))`);
2139
+ this.emit(`return text`);
2140
+ this.indent--;
2141
+ this.emit(`@staticmethod`);
2142
+ this.emit(`def set_lang(code): global __i18n_lang; __i18n_lang = code`);
2143
+ this.emit(`@staticmethod`);
2144
+ this.emit(`def get_lang(): return __i18n_lang`);
2145
+ this.indent--;
2146
+ this.emit(`i18n = I18n()`);
2147
+ this.emitRaw('');
2148
+ }
1519
2149
  }