naider 1.11.1 → 1.12.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.
- package/README.md +102 -15
- package/SPEC.naide +74 -0
- package/lsp/server.js +15 -1
- package/package.json +16 -3
- package/src/generator-bun.js +89 -0
- package/src/generator-python.js +441 -2
- package/src/generator.js +425 -4
- package/src/parser.js +200 -2
- package/src/tokens.js +12 -0
- package/vscode-naide/syntaxes/naide.tmLanguage.json +1 -1
package/src/generator-python.js
CHANGED
|
@@ -141,7 +141,13 @@ 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);
|
|
145
151
|
default:
|
|
146
152
|
this.emit(`# unknown: ${node.type}`);
|
|
147
153
|
}
|
|
@@ -447,6 +453,8 @@ export class PythonGenerator {
|
|
|
447
453
|
this.visitSession(node.name, child);
|
|
448
454
|
} else if (child.type === 'CacheDecl') {
|
|
449
455
|
this.visitCache(node.name, child);
|
|
456
|
+
} else if (child.type === 'GraphqlDecl') {
|
|
457
|
+
this.visitGraphql(node.name, child);
|
|
450
458
|
} else {
|
|
451
459
|
this.visitStatement(child);
|
|
452
460
|
}
|
|
@@ -1427,11 +1435,24 @@ export class PythonGenerator {
|
|
|
1427
1435
|
this.emit(`# cache: ${path} for ${ttl}`);
|
|
1428
1436
|
}
|
|
1429
1437
|
|
|
1430
|
-
// =====
|
|
1438
|
+
// ===== Bot (multi-platform) =====
|
|
1431
1439
|
|
|
1432
1440
|
visitBot(node) {
|
|
1433
|
-
const tokenExpr = node.token ? this.expr(node.token) : "os.environ.get('
|
|
1441
|
+
const tokenExpr = node.token ? this.expr(node.token) : "os.environ.get('BOT_TOKEN')";
|
|
1442
|
+
const botType = this.rawBotType(node.botType);
|
|
1434
1443
|
|
|
1444
|
+
if (botType === 'slack') return this.visitSlackBot(node, tokenExpr);
|
|
1445
|
+
if (botType === 'telegram') return this.visitTelegramBot(node, tokenExpr);
|
|
1446
|
+
if (botType === 'line') return this.visitLineBot(node, tokenExpr);
|
|
1447
|
+
return this.visitDiscordBot(node, tokenExpr);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
rawBotType(bt) {
|
|
1451
|
+
if (!bt) return 'discord';
|
|
1452
|
+
return bt.raw || bt.parts?.map(p => p.value).join('') || 'discord';
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
visitDiscordBot(node, tokenExpr) {
|
|
1435
1456
|
this.addImport('discord');
|
|
1436
1457
|
this.addFromImport('discord.ext', 'commands');
|
|
1437
1458
|
this.emitRaw('');
|
|
@@ -1496,6 +1517,424 @@ export class PythonGenerator {
|
|
|
1496
1517
|
this.emitRaw('');
|
|
1497
1518
|
}
|
|
1498
1519
|
|
|
1520
|
+
visitSlackBot(node, tokenExpr) {
|
|
1521
|
+
this.addFromImport('slack_bolt', 'App');
|
|
1522
|
+
this.emitRaw('');
|
|
1523
|
+
this.emit(`${node.name} = App(token=${tokenExpr}, signing_secret=os.environ.get('SLACK_SIGNING_SECRET'))`);
|
|
1524
|
+
this.usesOs = true;
|
|
1525
|
+
this.emitRaw('');
|
|
1526
|
+
|
|
1527
|
+
for (const handler of node.handlers) {
|
|
1528
|
+
if (handler.type === 'BotEvent') {
|
|
1529
|
+
const evtName = handler.event.raw || handler.event.parts?.map(p => p.value).join('');
|
|
1530
|
+
if (evtName === 'message') {
|
|
1531
|
+
this.emit(`@${node.name}.message("")`);
|
|
1532
|
+
this.emit(`def handle_message(message, say):`);
|
|
1533
|
+
} else {
|
|
1534
|
+
this.emit(`@${node.name}.event("${evtName}")`);
|
|
1535
|
+
this.emit(`def handle_${evtName}(event):`);
|
|
1536
|
+
}
|
|
1537
|
+
this.indent++;
|
|
1538
|
+
if (handler.body.length === 0) { this.emit('pass'); } else {
|
|
1539
|
+
for (const stmt of handler.body) this.visitStatement(stmt);
|
|
1540
|
+
}
|
|
1541
|
+
this.indent--;
|
|
1542
|
+
this.emitRaw('');
|
|
1543
|
+
} else if (handler.type === 'BotSlashCmd') {
|
|
1544
|
+
const cmdName = handler.name.raw || handler.name.parts?.map(p => p.value).join('');
|
|
1545
|
+
this.emit(`@${node.name}.command("/${cmdName}")`);
|
|
1546
|
+
this.emit(`def handle_${cmdName}(ack, respond, command):`);
|
|
1547
|
+
this.indent++;
|
|
1548
|
+
this.emit('ack()');
|
|
1549
|
+
for (const stmt of handler.body) this.visitStatement(stmt);
|
|
1550
|
+
this.indent--;
|
|
1551
|
+
this.emitRaw('');
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
this.emit(`if __name__ == '__main__':`);
|
|
1556
|
+
this.indent++;
|
|
1557
|
+
this.emit(`${node.name}.start(port=3000)`);
|
|
1558
|
+
this.indent--;
|
|
1559
|
+
this.emitRaw('');
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
visitTelegramBot(node, tokenExpr) {
|
|
1563
|
+
this.addFromImport('telegram.ext', 'ApplicationBuilder');
|
|
1564
|
+
this.addFromImport('telegram.ext', 'MessageHandler');
|
|
1565
|
+
this.addFromImport('telegram.ext', 'CommandHandler');
|
|
1566
|
+
this.addFromImport('telegram.ext', 'filters');
|
|
1567
|
+
this.emitRaw('');
|
|
1568
|
+
this.emit(`${node.name} = ApplicationBuilder().token(${tokenExpr}).build()`);
|
|
1569
|
+
this.emitRaw('');
|
|
1570
|
+
|
|
1571
|
+
for (const handler of node.handlers) {
|
|
1572
|
+
if (handler.type === 'BotEvent') {
|
|
1573
|
+
const evtName = handler.event.raw || handler.event.parts?.map(p => p.value).join('');
|
|
1574
|
+
const params = handler.params.length > 0 ? handler.params.join(', ') : 'update, context';
|
|
1575
|
+
this.emit(`async def handle_${evtName}(${params}):`);
|
|
1576
|
+
this.indent++;
|
|
1577
|
+
if (handler.body.length === 0) { this.emit('pass'); } else {
|
|
1578
|
+
for (const stmt of handler.body) this.visitStatement(stmt);
|
|
1579
|
+
}
|
|
1580
|
+
this.indent--;
|
|
1581
|
+
this.emit(`${node.name}.add_handler(MessageHandler(filters.TEXT, handle_${evtName}))`);
|
|
1582
|
+
this.emitRaw('');
|
|
1583
|
+
} else if (handler.type === 'BotSlashCmd') {
|
|
1584
|
+
const cmdName = handler.name.raw || handler.name.parts?.map(p => p.value).join('');
|
|
1585
|
+
this.emit(`async def cmd_${cmdName}(update, context):`);
|
|
1586
|
+
this.indent++;
|
|
1587
|
+
if (handler.body.length === 0) { this.emit('pass'); } else {
|
|
1588
|
+
for (const stmt of handler.body) this.visitStatement(stmt);
|
|
1589
|
+
}
|
|
1590
|
+
this.indent--;
|
|
1591
|
+
this.emit(`${node.name}.add_handler(CommandHandler("${cmdName}", cmd_${cmdName}))`);
|
|
1592
|
+
this.emitRaw('');
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
this.emit(`${node.name}.run_polling()`);
|
|
1597
|
+
this.emitRaw('');
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
visitLineBot(node, tokenExpr) {
|
|
1601
|
+
this.addFromImport('linebot', 'LineBotApi');
|
|
1602
|
+
this.addFromImport('linebot', 'WebhookHandler');
|
|
1603
|
+
this.addFromImport('flask', 'Flask');
|
|
1604
|
+
this.addFromImport('flask', 'request');
|
|
1605
|
+
this.addFromImport('flask', 'abort');
|
|
1606
|
+
this.emitRaw('');
|
|
1607
|
+
this.emit(`${node.name} = LineBotApi(${tokenExpr})`);
|
|
1608
|
+
this.emit(`__handler = WebhookHandler(os.environ.get('LINE_CHANNEL_SECRET'))`);
|
|
1609
|
+
this.usesOs = true;
|
|
1610
|
+
this.emit(`__app = Flask(__name__)`);
|
|
1611
|
+
this.emitRaw('');
|
|
1612
|
+
|
|
1613
|
+
this.emit(`@__app.route('/webhook', methods=['POST'])`);
|
|
1614
|
+
this.emit(`def webhook():`);
|
|
1615
|
+
this.indent++;
|
|
1616
|
+
this.emit(`signature = request.headers['X-Line-Signature']`);
|
|
1617
|
+
this.emit(`body = request.get_data(as_text=True)`);
|
|
1618
|
+
this.emit(`__handler.handle(body, signature)`);
|
|
1619
|
+
this.emit(`return 'OK'`);
|
|
1620
|
+
this.indent--;
|
|
1621
|
+
this.emitRaw('');
|
|
1622
|
+
|
|
1623
|
+
for (const handler of node.handlers) {
|
|
1624
|
+
if (handler.type === 'BotEvent') {
|
|
1625
|
+
const evtName = handler.event.raw || handler.event.parts?.map(p => p.value).join('');
|
|
1626
|
+
if (evtName === 'message') {
|
|
1627
|
+
this.emit(`@__handler.add(MessageEvent, message=TextMessage)`);
|
|
1628
|
+
this.emit(`def handle_message(event):`);
|
|
1629
|
+
} else {
|
|
1630
|
+
this.emit(`@__handler.add(${evtName})`);
|
|
1631
|
+
this.emit(`def handle_${evtName}(event):`);
|
|
1632
|
+
}
|
|
1633
|
+
this.indent++;
|
|
1634
|
+
if (handler.body.length === 0) { this.emit('pass'); } else {
|
|
1635
|
+
for (const stmt of handler.body) this.visitStatement(stmt);
|
|
1636
|
+
}
|
|
1637
|
+
this.indent--;
|
|
1638
|
+
this.emitRaw('');
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
this.emit(`if __name__ == '__main__':`);
|
|
1643
|
+
this.indent++;
|
|
1644
|
+
this.emit(`__app.run(port=3000)`);
|
|
1645
|
+
this.indent--;
|
|
1646
|
+
this.emitRaw('');
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// ===== Every (cron/scheduler) =====
|
|
1650
|
+
|
|
1651
|
+
visitEvery(node) {
|
|
1652
|
+
const raw = node.interval?.value?.raw || node.interval?.value?.parts?.map(p => p.value).join('') || '';
|
|
1653
|
+
const isCron = /^[\d*\/,-]+\s+[\d*\/,-]+\s+[\d*\/,-]+\s+[\d*\/,-]+\s+[\d*\/,-]+/.test(raw.trim());
|
|
1654
|
+
|
|
1655
|
+
if (isCron) {
|
|
1656
|
+
this.addFromImport('apscheduler.schedulers.blocking', 'BlockingScheduler');
|
|
1657
|
+
this.addFromImport('apscheduler.triggers.cron', 'CronTrigger');
|
|
1658
|
+
this.emitRaw('');
|
|
1659
|
+
const parts = raw.trim().split(/\s+/);
|
|
1660
|
+
this.emit(`__scheduler = BlockingScheduler()`);
|
|
1661
|
+
this.emit(`def __every_job():`);
|
|
1662
|
+
this.indent++;
|
|
1663
|
+
if (node.body.length === 0) { this.emit('pass'); } else {
|
|
1664
|
+
for (const stmt of node.body) this.visitStatement(stmt);
|
|
1665
|
+
}
|
|
1666
|
+
this.indent--;
|
|
1667
|
+
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]}'))`);
|
|
1668
|
+
this.emit(`__scheduler.start()`);
|
|
1669
|
+
} else {
|
|
1670
|
+
this.addImport('schedule');
|
|
1671
|
+
this.addImport('time');
|
|
1672
|
+
this.emitRaw('');
|
|
1673
|
+
const interval = this.expr(node.interval);
|
|
1674
|
+
this.emit(`def __every_job():`);
|
|
1675
|
+
this.indent++;
|
|
1676
|
+
if (node.body.length === 0) { this.emit('pass'); } else {
|
|
1677
|
+
for (const stmt of node.body) this.visitStatement(stmt);
|
|
1678
|
+
}
|
|
1679
|
+
this.indent--;
|
|
1680
|
+
this.emit(`schedule.every(${interval}).do(__every_job)`);
|
|
1681
|
+
this.emit(`while True:`);
|
|
1682
|
+
this.indent++;
|
|
1683
|
+
this.emit(`schedule.run_pending()`);
|
|
1684
|
+
this.emit(`time.sleep(1)`);
|
|
1685
|
+
this.indent--;
|
|
1686
|
+
}
|
|
1687
|
+
this.emitRaw('');
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
// ===== GraphQL =====
|
|
1691
|
+
|
|
1692
|
+
visitGraphql(appName, node) {
|
|
1693
|
+
const path = this.generateString(node.path);
|
|
1694
|
+
this.addFromImport('flask_graphql', 'GraphQLView');
|
|
1695
|
+
this.addFromImport('graphene', '*');
|
|
1696
|
+
this.emitRaw('');
|
|
1697
|
+
|
|
1698
|
+
const schemaNames = [...(this.schemas || new Map()).keys()];
|
|
1699
|
+
if (schemaNames.length > 0) {
|
|
1700
|
+
for (const name of schemaNames) {
|
|
1701
|
+
const schema = this.schemas.get(name);
|
|
1702
|
+
this.emit(`class ${name}Type(ObjectType):`);
|
|
1703
|
+
this.indent++;
|
|
1704
|
+
for (const f of schema.fields) {
|
|
1705
|
+
const gqlType = this.toGraphqlType(f.type);
|
|
1706
|
+
this.emit(`${f.name} = ${gqlType}()`);
|
|
1707
|
+
}
|
|
1708
|
+
this.indent--;
|
|
1709
|
+
this.emitRaw('');
|
|
1710
|
+
}
|
|
1711
|
+
this.emit(`class Query(ObjectType):`);
|
|
1712
|
+
this.indent++;
|
|
1713
|
+
for (const name of schemaNames) {
|
|
1714
|
+
this.emit(`${name.toLowerCase()}s = List(${name}Type)`);
|
|
1715
|
+
this.emit(`def resolve_${name.toLowerCase()}s(self, info):`);
|
|
1716
|
+
this.indent++;
|
|
1717
|
+
this.emit(`return []`);
|
|
1718
|
+
this.indent--;
|
|
1719
|
+
}
|
|
1720
|
+
this.indent--;
|
|
1721
|
+
} else {
|
|
1722
|
+
this.emit(`class Query(ObjectType):`);
|
|
1723
|
+
this.indent++;
|
|
1724
|
+
this.emit(`hello = String()`);
|
|
1725
|
+
this.emit(`def resolve_hello(self, info):`);
|
|
1726
|
+
this.indent++;
|
|
1727
|
+
this.emit(`return 'Hello from NAIDE GraphQL'`);
|
|
1728
|
+
this.indent--;
|
|
1729
|
+
this.indent--;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
this.emitRaw('');
|
|
1733
|
+
this.emit(`__gql_schema = Schema(query=Query)`);
|
|
1734
|
+
const rawPath = this.rawString(node.path);
|
|
1735
|
+
this.emit(`${appName}.add_url_rule(${JSON.stringify(rawPath)}, view_func=GraphQLView.as_view('graphql', schema=__gql_schema, graphiql=True))`);
|
|
1736
|
+
this.emitRaw('');
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
toGraphqlType(naideType) {
|
|
1740
|
+
const map = { 'str': 'graphene.String', 'int': 'graphene.Int', 'num': 'graphene.Float', 'bool': 'graphene.Boolean', 'auto': 'graphene.ID', 'timestamp': 'graphene.String', 'enum': 'graphene.String' };
|
|
1741
|
+
return map[naideType] || 'graphene.String';
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
// ===== Page (HTML generation) =====
|
|
1745
|
+
|
|
1746
|
+
visitPage(node) {
|
|
1747
|
+
const filename = this.rawString(node.filename);
|
|
1748
|
+
const headParts = [];
|
|
1749
|
+
const bodyParts = [];
|
|
1750
|
+
let pageTitle = 'NAIDE Page';
|
|
1751
|
+
|
|
1752
|
+
for (const el of node.elements) {
|
|
1753
|
+
this.classifyPageElement(el, headParts, bodyParts, (t) => { pageTitle = t; });
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
this.emit(`with open(${JSON.stringify(filename)}, 'w') as __f:`);
|
|
1757
|
+
this.indent++;
|
|
1758
|
+
this.emit(`__f.write("""<!DOCTYPE html>`);
|
|
1759
|
+
this.emit(`<html lang="en">`);
|
|
1760
|
+
this.emit(`<head>`);
|
|
1761
|
+
this.emit(`<meta charset="UTF-8">`);
|
|
1762
|
+
this.emit(`<meta name="viewport" content="width=device-width, initial-scale=1.0">`);
|
|
1763
|
+
this.emit(`<title>${pageTitle}</title>`);
|
|
1764
|
+
for (const h of headParts) this.emit(h);
|
|
1765
|
+
this.emit(`</head>`);
|
|
1766
|
+
this.emit(`<body>`);
|
|
1767
|
+
for (const b of bodyParts) this.emit(b);
|
|
1768
|
+
this.emit(`</body>`);
|
|
1769
|
+
this.emit(`</html>""")`);
|
|
1770
|
+
this.indent--;
|
|
1771
|
+
this.emit(`print(f"Generated: ${filename}")`);
|
|
1772
|
+
this.emitRaw('');
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
classifyPageElement(el, head, body, setTitle) {
|
|
1776
|
+
const tag = el.tag;
|
|
1777
|
+
const arg0 = el.args[0] ? this.rawString(el.args[0]) : '';
|
|
1778
|
+
const arg1 = el.args[1] ? this.rawString(el.args[1]) : '';
|
|
1779
|
+
if (tag === 'title') { setTitle(arg0); return; }
|
|
1780
|
+
if (tag === 'style' || tag === 'link') { head.push(`<link rel="stylesheet" href="${arg0}">`); return; }
|
|
1781
|
+
if (tag === 'meta') { head.push(`<meta name="${arg0}" content="${arg1}">`); return; }
|
|
1782
|
+
if (tag === 'script') { body.push(`<script src="${arg0}"></script>`); return; }
|
|
1783
|
+
if (tag === 'img') { body.push(`<img src="${arg0}" alt="${arg1}">`); return; }
|
|
1784
|
+
if (tag === 'a') { body.push(`<a href="${arg0}">${arg1}</a>`); return; }
|
|
1785
|
+
if (tag === 'input') { body.push(`<input type="${arg0}" name="${arg1}">`); return; }
|
|
1786
|
+
if (el.children && el.children.length > 0) {
|
|
1787
|
+
const cls = arg0 ? ` class="${arg0}"` : '';
|
|
1788
|
+
body.push(`<${tag}${cls}>`);
|
|
1789
|
+
for (const child of el.children) this.classifyPageElement(child, head, body, setTitle);
|
|
1790
|
+
body.push(`</${tag}>`);
|
|
1791
|
+
} else {
|
|
1792
|
+
body.push(`<${tag}>${arg0}</${tag}>`);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
// ===== CLI App =====
|
|
1797
|
+
|
|
1798
|
+
visitCli(node) {
|
|
1799
|
+
this.addImport('argparse');
|
|
1800
|
+
this.emitRaw('');
|
|
1801
|
+
const desc = node.description ? this.rawString(node.description) : node.name;
|
|
1802
|
+
this.emit(`__parser = argparse.ArgumentParser(description=${JSON.stringify(desc)})`);
|
|
1803
|
+
|
|
1804
|
+
for (const arg of node.args) {
|
|
1805
|
+
const argName = this.rawString(arg.name);
|
|
1806
|
+
const pyType = arg.type === 'int' ? 'int' : arg.type === 'num' ? 'float' : 'str';
|
|
1807
|
+
const desc = arg.description ? `, help=${JSON.stringify(this.rawString(arg.description))}` : '';
|
|
1808
|
+
this.emit(`__parser.add_argument('--${argName}', type=${pyType}${desc})`);
|
|
1809
|
+
}
|
|
1810
|
+
for (const flag of node.flags) {
|
|
1811
|
+
const s = this.rawString(flag.short);
|
|
1812
|
+
const l = this.rawString(flag.long);
|
|
1813
|
+
const desc = flag.description ? `, help=${JSON.stringify(this.rawString(flag.description))}` : '';
|
|
1814
|
+
this.emit(`__parser.add_argument('-${s}', '--${l}', action='store_true'${desc})`);
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
this.emit(`args = __parser.parse_args()`);
|
|
1818
|
+
this.emitRaw('');
|
|
1819
|
+
|
|
1820
|
+
if (node.run) {
|
|
1821
|
+
const runParam = node.run.params.length > 0 ? node.run.params[0] : 'args';
|
|
1822
|
+
if (runParam !== 'args') {
|
|
1823
|
+
this.emit(`${runParam} = args`);
|
|
1824
|
+
}
|
|
1825
|
+
for (const stmt of node.run.body) this.visitStatement(stmt);
|
|
1826
|
+
}
|
|
1827
|
+
this.emitRaw('');
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
// ===== Mail =====
|
|
1831
|
+
|
|
1832
|
+
visitMail(node) {
|
|
1833
|
+
this.addImport('smtplib');
|
|
1834
|
+
this.addFromImport('email.mime.text', 'MIMEText');
|
|
1835
|
+
this.addFromImport('email.mime.multipart', 'MIMEMultipart');
|
|
1836
|
+
this.usesOs = true;
|
|
1837
|
+
this.emitRaw('');
|
|
1838
|
+
|
|
1839
|
+
const host = this.generateString(node.host);
|
|
1840
|
+
const port = this.expr(node.port);
|
|
1841
|
+
const user = node.user ? this.expr(node.user) : "os.environ.get('MAIL_USER')";
|
|
1842
|
+
const pass = node.pass ? this.expr(node.pass) : "os.environ.get('MAIL_PASS')";
|
|
1843
|
+
|
|
1844
|
+
this.emit(`class Mail:`);
|
|
1845
|
+
this.indent++;
|
|
1846
|
+
this.emit(`def __init__(self, host, port, user, password):`);
|
|
1847
|
+
this.indent++;
|
|
1848
|
+
this.emit(`self.host = host`);
|
|
1849
|
+
this.emit(`self.port = port`);
|
|
1850
|
+
this.emit(`self.user = user`);
|
|
1851
|
+
this.emit(`self.password = password`);
|
|
1852
|
+
this.indent--;
|
|
1853
|
+
this.emitRaw('');
|
|
1854
|
+
this.emit(`def send(self, to, subject, body):`);
|
|
1855
|
+
this.indent++;
|
|
1856
|
+
this.emit(`msg = MIMEText(body)`);
|
|
1857
|
+
this.emit(`msg['Subject'] = subject`);
|
|
1858
|
+
this.emit(`msg['From'] = self.user`);
|
|
1859
|
+
this.emit(`msg['To'] = to`);
|
|
1860
|
+
this.emit(`with smtplib.SMTP(self.host, self.port) as server:`);
|
|
1861
|
+
this.indent++;
|
|
1862
|
+
this.emit(`server.starttls()`);
|
|
1863
|
+
this.emit(`server.login(self.user, self.password)`);
|
|
1864
|
+
this.emit(`server.send_message(msg)`);
|
|
1865
|
+
this.indent--;
|
|
1866
|
+
this.indent--;
|
|
1867
|
+
this.indent--;
|
|
1868
|
+
this.emitRaw('');
|
|
1869
|
+
this.emit(`mail = Mail(${host}, ${port}, ${user}, ${pass})`);
|
|
1870
|
+
this.emitRaw('');
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// ===== Desktop (pywebview) =====
|
|
1874
|
+
|
|
1875
|
+
visitDesktop(node) {
|
|
1876
|
+
this.addImport('webview');
|
|
1877
|
+
this.emitRaw('');
|
|
1878
|
+
const title = node.title ? this.rawString(node.title) : node.name;
|
|
1879
|
+
const width = node.width ? this.expr(node.width) : '800';
|
|
1880
|
+
const height = node.height ? this.expr(node.height) : '600';
|
|
1881
|
+
const load = node.load ? this.expr(node.load) : '"index.html"';
|
|
1882
|
+
this.emit(`webview.create_window(${JSON.stringify(title)}, ${load}, width=${width}, height=${height})`);
|
|
1883
|
+
this.emit(`webview.start()`);
|
|
1884
|
+
this.emitRaw('');
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// ===== Screen (Kivy) =====
|
|
1888
|
+
|
|
1889
|
+
visitScreen(node) {
|
|
1890
|
+
this.addFromImport('kivy.app', 'App');
|
|
1891
|
+
this.addFromImport('kivy.uix.boxlayout', 'BoxLayout');
|
|
1892
|
+
this.addFromImport('kivy.uix.label', 'Label');
|
|
1893
|
+
this.addFromImport('kivy.uix.button', 'Button');
|
|
1894
|
+
this.addFromImport('kivy.uix.textinput', 'TextInput');
|
|
1895
|
+
this.emitRaw('');
|
|
1896
|
+
|
|
1897
|
+
this.emit(`class ${node.name}Screen(BoxLayout):`);
|
|
1898
|
+
this.indent++;
|
|
1899
|
+
this.emit(`def __init__(self, **kwargs):`);
|
|
1900
|
+
this.indent++;
|
|
1901
|
+
this.emit(`super().__init__(**kwargs)`);
|
|
1902
|
+
this.emit(`self.orientation = 'vertical'`);
|
|
1903
|
+
this.emit(`self.padding = 16`);
|
|
1904
|
+
|
|
1905
|
+
for (const el of node.elements) {
|
|
1906
|
+
const tag = el.tag;
|
|
1907
|
+
const arg0 = el.args[0] ? this.rawString(el.args[0]) : '';
|
|
1908
|
+
if (tag === 'text') {
|
|
1909
|
+
this.emit(`self.add_widget(Label(text=${JSON.stringify(arg0)}))`);
|
|
1910
|
+
} else if (tag === 'button') {
|
|
1911
|
+
this.emit(`self.add_widget(Button(text=${JSON.stringify(arg0)}))`);
|
|
1912
|
+
} else if (tag === 'input') {
|
|
1913
|
+
this.emit(`self.add_widget(TextInput(hint_text=${JSON.stringify(arg0)}))`);
|
|
1914
|
+
} else {
|
|
1915
|
+
this.emit(`self.add_widget(Label(text=${JSON.stringify(arg0)}))`);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
this.indent--;
|
|
1920
|
+
this.indent--;
|
|
1921
|
+
this.emitRaw('');
|
|
1922
|
+
|
|
1923
|
+
this.emit(`class ${node.name}App(App):`);
|
|
1924
|
+
this.indent++;
|
|
1925
|
+
this.emit(`def build(self):`);
|
|
1926
|
+
this.indent++;
|
|
1927
|
+
this.emit(`return ${node.name}Screen()`);
|
|
1928
|
+
this.indent--;
|
|
1929
|
+
this.indent--;
|
|
1930
|
+
this.emitRaw('');
|
|
1931
|
+
this.emit(`if __name__ == '__main__':`);
|
|
1932
|
+
this.indent++;
|
|
1933
|
+
this.emit(`${node.name}App().run()`);
|
|
1934
|
+
this.indent--;
|
|
1935
|
+
this.emitRaw('');
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1499
1938
|
// ===== Prompt template =====
|
|
1500
1939
|
|
|
1501
1940
|
visitPrompt(node) {
|