naider 1.13.0 → 1.14.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 +29 -0
- package/SPEC.naide +20 -0
- package/lsp/server.js +4 -1
- package/package.json +1 -1
- package/src/generator-python.js +102 -0
- package/src/generator.js +109 -0
- package/src/parser.js +81 -1
- package/src/tokens.js +6 -0
- package/vscode-naide/syntaxes/naide.tmLanguage.json +1 -1
package/README.md
CHANGED
|
@@ -727,6 +727,35 @@ i18n "locales/":
|
|
|
727
727
|
|
|
728
728
|
Usage: `i18n.t("greeting.hello")`, `i18n.setLang("ja")`.
|
|
729
729
|
|
|
730
|
+
## Push Notifications
|
|
731
|
+
|
|
732
|
+
```python
|
|
733
|
+
push env.VAPID_PUBLIC env.VAPID_PRIVATE:
|
|
734
|
+
endpoint "/subscribe"
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
Usage: `push.send(subscription, "Title", "Body")`. Compiles to web-push (Node.js) or pywebpush (Python).
|
|
738
|
+
|
|
739
|
+
## Full-Text Search
|
|
740
|
+
|
|
741
|
+
```python
|
|
742
|
+
search "meilisearch" "http://localhost:7700" env.MEILI_KEY:
|
|
743
|
+
index "products"
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
Usage: `search.query("keyword")`, `search.add(docs)`. Supports Meilisearch and Elasticsearch.
|
|
747
|
+
|
|
748
|
+
## Image Processing
|
|
749
|
+
|
|
750
|
+
```python
|
|
751
|
+
image "photo.jpg" -> "output.jpg":
|
|
752
|
+
resize 800 600
|
|
753
|
+
grayscale
|
|
754
|
+
watermark "logo.png"
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
Operations: `resize`, `crop`, `rotate`, `blur`, `grayscale`, `flip`, `watermark`, `format`. Compiles to sharp (Node.js) or Pillow (Python).
|
|
758
|
+
|
|
730
759
|
## Environment Variables
|
|
731
760
|
|
|
732
761
|
```python
|
package/SPEC.naide
CHANGED
|
@@ -564,3 +564,23 @@ i18n "locales/":
|
|
|
564
564
|
lang "ja" "ja.json"
|
|
565
565
|
# usage: i18n.t("greeting.hello")
|
|
566
566
|
# usage: i18n.setLang("ja")
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
# ---- Push通知 ----
|
|
570
|
+
push env.VAPID_PUBLIC env.VAPID_PRIVATE:
|
|
571
|
+
endpoint "/subscribe"
|
|
572
|
+
# usage: push.send(subscription, "Title", "Body")
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
# ---- 全文検索 ----
|
|
576
|
+
search "meilisearch" "http://localhost:7700" env.MEILI_KEY:
|
|
577
|
+
index "products"
|
|
578
|
+
# usage: search.query("keyword")
|
|
579
|
+
# usage: search.add([{id: 1, name: "Item"}])
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
# ---- 画像処理 ----
|
|
583
|
+
image "photo.jpg" -> "output.jpg":
|
|
584
|
+
resize 800 600
|
|
585
|
+
grayscale
|
|
586
|
+
watermark "logo.png"
|
package/lsp/server.js
CHANGED
|
@@ -289,7 +289,7 @@ function getCompletions() {
|
|
|
289
289
|
'cors', 'auth', 'crud', 'limit', 'cookie', 'session', 'static', 'ws', 'sse',
|
|
290
290
|
'cache', 'view', 'upload', 'group', 'validate', 'openapi', 'error', 'mid', 'prompt',
|
|
291
291
|
'page', 'cli', 'mail', 'graphql', 'desktop', 'screen',
|
|
292
|
-
'oauth', 'pay', 'storage', 'pdf', 'i18n',
|
|
292
|
+
'oauth', 'pay', 'storage', 'pdf', 'i18n', 'push', 'search', 'image',
|
|
293
293
|
];
|
|
294
294
|
const builtins = [
|
|
295
295
|
{ label: 'uuid()', detail: 'Generate UUID v4', insertText: 'uuid()' },
|
|
@@ -351,6 +351,9 @@ const HOVER_DOCS = {
|
|
|
351
351
|
'storage': '**storage** — Cloud storage (S3/GCS)\n```naide\nstorage "s3" env.BUCKET env.KEY env.SECRET:\n region "ap-northeast-1"\n```\nUsage: `storage.upload(key, body)`, `storage.download(key)`',
|
|
352
352
|
'pdf': '**pdf** — PDF generation\n```naide\npdf "report.pdf":\n title "Report"\n text "Hello"\n```',
|
|
353
353
|
'i18n': '**i18n** — Internationalization\n```naide\ni18n "locales/":\n default "en"\n lang "en" "en.json"\n lang "ja" "ja.json"\n```\nUsage: `i18n.t("key")`, `i18n.setLang("ja")`',
|
|
354
|
+
'push': '**push** — Push notifications\n```naide\npush env.VAPID_PUBLIC env.VAPID_PRIVATE:\n endpoint "/subscribe"\n```\nUsage: `push.send(subscription, title, body)`',
|
|
355
|
+
'search': '**search** — Full-text search\n```naide\nsearch "meilisearch" "http://localhost:7700" env.KEY:\n index "products"\n```\nUsage: `search.query("keyword")`, `search.add(docs)`',
|
|
356
|
+
'image': '**image** — Image processing\n```naide\nimage "input.jpg" -> "output.jpg":\n resize 800 600\n grayscale\n watermark "logo.png"\n```',
|
|
354
357
|
};
|
|
355
358
|
|
|
356
359
|
function getHover(params) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naider",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "NAIDE - Node AI Development Environment. AI-specialized language with built-in server, auth, DB, SQL, AI/LLM, WebSocket, bots (Discord/Slack/Telegram/LINE), CLI, HTML, email, GraphQL, desktop, mobile, and more — transpiles to Node.js/Python/Bun.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
package/src/generator-python.js
CHANGED
|
@@ -153,6 +153,9 @@ export class PythonGenerator {
|
|
|
153
153
|
case 'StorageDecl': return this.visitStorage(node);
|
|
154
154
|
case 'PdfDecl': return this.visitPdf(node);
|
|
155
155
|
case 'I18nDecl': return this.visitI18n(node);
|
|
156
|
+
case 'PushDecl': return this.visitPush(node);
|
|
157
|
+
case 'SearchDecl': return this.visitSearch(node);
|
|
158
|
+
case 'ImageDecl': return this.visitImage(node);
|
|
156
159
|
default:
|
|
157
160
|
this.emit(`# unknown: ${node.type}`);
|
|
158
161
|
}
|
|
@@ -2146,4 +2149,103 @@ export class PythonGenerator {
|
|
|
2146
2149
|
this.emit(`i18n = I18n()`);
|
|
2147
2150
|
this.emitRaw('');
|
|
2148
2151
|
}
|
|
2152
|
+
|
|
2153
|
+
// ===== Push =====
|
|
2154
|
+
visitPush(node) {
|
|
2155
|
+
const publicKey = this.expr(node.publicKey);
|
|
2156
|
+
const privateKey = this.expr(node.privateKey);
|
|
2157
|
+
this.addFromImport('pywebpush', 'webpush');
|
|
2158
|
+
this.emitRaw('');
|
|
2159
|
+
this.emit(`class Push:`);
|
|
2160
|
+
this.indent++;
|
|
2161
|
+
this.emit(`VAPID_PUBLIC = ${publicKey}`);
|
|
2162
|
+
this.emit(`VAPID_PRIVATE = ${privateKey}`);
|
|
2163
|
+
this.emit(`@staticmethod`);
|
|
2164
|
+
this.emit(`def send(subscription, title, body):`);
|
|
2165
|
+
this.indent++;
|
|
2166
|
+
this.emit(`import json`);
|
|
2167
|
+
this.emit(`webpush(subscription_info=subscription, data=json.dumps({'title': title, 'body': body}), vapid_private_key=Push.VAPID_PRIVATE, vapid_claims={'sub': 'mailto:noreply@example.com'})`);
|
|
2168
|
+
this.indent--;
|
|
2169
|
+
this.indent--;
|
|
2170
|
+
this.emit(`push = Push()`);
|
|
2171
|
+
this.emitRaw('');
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
// ===== Search =====
|
|
2175
|
+
visitSearch(node) {
|
|
2176
|
+
const engine = this.rawString(node.engine);
|
|
2177
|
+
const host = this.expr(node.host);
|
|
2178
|
+
const apiKey = this.expr(node.apiKey);
|
|
2179
|
+
const index = node.index ? this.rawString(node.index) : 'default';
|
|
2180
|
+
|
|
2181
|
+
if (engine === 'meilisearch') {
|
|
2182
|
+
this.addImport('meilisearch');
|
|
2183
|
+
this.emitRaw('');
|
|
2184
|
+
this.emit(`__search_client = meilisearch.Client(${host}, ${apiKey})`);
|
|
2185
|
+
this.emit(`__search_index = __search_client.index(${JSON.stringify(index)})`);
|
|
2186
|
+
this.emitRaw('');
|
|
2187
|
+
this.emit(`class Search:`);
|
|
2188
|
+
this.indent++;
|
|
2189
|
+
this.emit(`@staticmethod`);
|
|
2190
|
+
this.emit(`def query(q, **opts): return __search_index.search(q, opts)`);
|
|
2191
|
+
this.emit(`@staticmethod`);
|
|
2192
|
+
this.emit(`def add(docs): return __search_index.add_documents(docs)`);
|
|
2193
|
+
this.emit(`@staticmethod`);
|
|
2194
|
+
this.emit(`def remove(doc_id): return __search_index.delete_document(doc_id)`);
|
|
2195
|
+
this.indent--;
|
|
2196
|
+
} else {
|
|
2197
|
+
this.addFromImport('elasticsearch', 'Elasticsearch');
|
|
2198
|
+
this.emitRaw('');
|
|
2199
|
+
this.emit(`__es = Elasticsearch(${host}, api_key=${apiKey})`);
|
|
2200
|
+
this.emitRaw('');
|
|
2201
|
+
this.emit(`class Search:`);
|
|
2202
|
+
this.indent++;
|
|
2203
|
+
this.emit(`@staticmethod`);
|
|
2204
|
+
this.emit(`def query(q, **opts): return __es.search(index=${JSON.stringify(index)}, query={'match': {'_all': q}}, **opts)`);
|
|
2205
|
+
this.emit(`@staticmethod`);
|
|
2206
|
+
this.emit(`def add(doc): return __es.index(index=${JSON.stringify(index)}, body=doc)`);
|
|
2207
|
+
this.emit(`@staticmethod`);
|
|
2208
|
+
this.emit(`def remove(doc_id): return __es.delete(index=${JSON.stringify(index)}, id=doc_id)`);
|
|
2209
|
+
this.indent--;
|
|
2210
|
+
}
|
|
2211
|
+
this.emit(`search = Search()`);
|
|
2212
|
+
this.emitRaw('');
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
// ===== Image =====
|
|
2216
|
+
visitImage(node) {
|
|
2217
|
+
const input = this.expr(node.input);
|
|
2218
|
+
const output = node.output ? this.expr(node.output) : input;
|
|
2219
|
+
this.addFromImport('PIL', 'Image as PILImage');
|
|
2220
|
+
this.emitRaw('');
|
|
2221
|
+
this.emit(`__img = PILImage.open(${input})`);
|
|
2222
|
+
|
|
2223
|
+
for (const op of node.operations) {
|
|
2224
|
+
if (op.op === 'resize') {
|
|
2225
|
+
const w = op.args[0] || 800;
|
|
2226
|
+
const h = op.args[1] || 600;
|
|
2227
|
+
this.emit(`__img = __img.resize((${w}, ${h}))`);
|
|
2228
|
+
} else if (op.op === 'crop') {
|
|
2229
|
+
const l = op.args[0] || 0, t = op.args[1] || 0, r = op.args[2] || 100, b = op.args[3] || 100;
|
|
2230
|
+
this.emit(`__img = __img.crop((${l}, ${t}, ${r}, ${b}))`);
|
|
2231
|
+
} else if (op.op === 'rotate') {
|
|
2232
|
+
this.emit(`__img = __img.rotate(${op.args[0] || 90})`);
|
|
2233
|
+
} else if (op.op === 'blur') {
|
|
2234
|
+
this.addFromImport('PIL.ImageFilter', 'GaussianBlur');
|
|
2235
|
+
this.emit(`__img = __img.filter(GaussianBlur(radius=${op.args[0] || 5}))`);
|
|
2236
|
+
} else if (op.op === 'grayscale' || op.op === 'greyscale') {
|
|
2237
|
+
this.emit(`__img = __img.convert('L')`);
|
|
2238
|
+
} else if (op.op === 'flip') {
|
|
2239
|
+
this.emit(`__img = __img.transpose(PILImage.FLIP_TOP_BOTTOM)`);
|
|
2240
|
+
} else if (op.op === 'watermark') {
|
|
2241
|
+
const wm = op.args[0] ? this.rawString(op.args[0]) : 'watermark.png';
|
|
2242
|
+
this.emit(`__wm = PILImage.open(${JSON.stringify(wm)})`);
|
|
2243
|
+
this.emit(`__img.paste(__wm, (0, 0), __wm)`);
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
this.emit(`__img.save(${output})`);
|
|
2248
|
+
this.emit(`print(f"Processed: {${output}}")`);
|
|
2249
|
+
this.emitRaw('');
|
|
2250
|
+
}
|
|
2149
2251
|
}
|
package/src/generator.js
CHANGED
|
@@ -153,6 +153,9 @@ export class Generator {
|
|
|
153
153
|
case 'StorageDecl': return this.visitStorage(node);
|
|
154
154
|
case 'PdfDecl': return this.visitPdf(node);
|
|
155
155
|
case 'I18nDecl': return this.visitI18n(node);
|
|
156
|
+
case 'PushDecl': return this.visitPush(node);
|
|
157
|
+
case 'SearchDecl': return this.visitSearch(node);
|
|
158
|
+
case 'ImageDecl': return this.visitImage(node);
|
|
156
159
|
default:
|
|
157
160
|
this.emit(`/* unknown: ${node.type} */`);
|
|
158
161
|
}
|
|
@@ -2062,4 +2065,110 @@ export class Generator {
|
|
|
2062
2065
|
this.emit(`};`);
|
|
2063
2066
|
this.emitRaw('');
|
|
2064
2067
|
}
|
|
2068
|
+
|
|
2069
|
+
// ===== Push Notifications =====
|
|
2070
|
+
|
|
2071
|
+
visitPush(node) {
|
|
2072
|
+
const publicKey = this.expr(node.publicKey);
|
|
2073
|
+
const privateKey = this.expr(node.privateKey);
|
|
2074
|
+
const endpoint = node.endpoint ? this.rawPageString(node.endpoint) : '/subscribe';
|
|
2075
|
+
|
|
2076
|
+
this.emit(`import webpush from 'web-push';`);
|
|
2077
|
+
this.emitRaw('');
|
|
2078
|
+
this.emit(`webpush.setVapidDetails('mailto:noreply@example.com', ${publicKey}, ${privateKey});`);
|
|
2079
|
+
this.emitRaw('');
|
|
2080
|
+
this.emit(`const push = {`);
|
|
2081
|
+
this.indent++;
|
|
2082
|
+
this.emit(`async send(subscription, title, body, data = {}) {`);
|
|
2083
|
+
this.indent++;
|
|
2084
|
+
this.emit(`return webpush.sendNotification(subscription, JSON.stringify({ title, body, data }));`);
|
|
2085
|
+
this.indent--;
|
|
2086
|
+
this.emit(`},`);
|
|
2087
|
+
this.emit(`async sendAll(subscriptions, title, body, data = {}) {`);
|
|
2088
|
+
this.indent++;
|
|
2089
|
+
this.emit(`return Promise.allSettled(subscriptions.map(sub => push.send(sub, title, body, data)));`);
|
|
2090
|
+
this.indent--;
|
|
2091
|
+
this.emit(`},`);
|
|
2092
|
+
this.indent--;
|
|
2093
|
+
this.emit(`};`);
|
|
2094
|
+
this.emitRaw('');
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// ===== Search =====
|
|
2098
|
+
|
|
2099
|
+
visitSearch(node) {
|
|
2100
|
+
const engine = this.rawPageString(node.engine);
|
|
2101
|
+
const host = this.expr(node.host);
|
|
2102
|
+
const apiKey = this.expr(node.apiKey);
|
|
2103
|
+
const index = node.index ? this.rawPageString(node.index) : 'default';
|
|
2104
|
+
|
|
2105
|
+
if (engine === 'meilisearch') {
|
|
2106
|
+
this.emit(`import { MeiliSearch } from 'meilisearch';`);
|
|
2107
|
+
this.emitRaw('');
|
|
2108
|
+
this.emit(`const __searchClient = new MeiliSearch({ host: ${host}, apiKey: ${apiKey} });`);
|
|
2109
|
+
this.emit(`const __searchIndex = __searchClient.index(${JSON.stringify(index)});`);
|
|
2110
|
+
this.emitRaw('');
|
|
2111
|
+
this.emit(`const search = {`);
|
|
2112
|
+
this.indent++;
|
|
2113
|
+
this.emit(`async query(q, opts = {}) { return __searchIndex.search(q, opts); },`);
|
|
2114
|
+
this.emit(`async add(docs) { return __searchIndex.addDocuments(docs); },`);
|
|
2115
|
+
this.emit(`async remove(id) { return __searchIndex.deleteDocument(id); },`);
|
|
2116
|
+
this.emit(`async update(docs) { return __searchIndex.updateDocuments(docs); },`);
|
|
2117
|
+
this.indent--;
|
|
2118
|
+
this.emit(`};`);
|
|
2119
|
+
} else {
|
|
2120
|
+
this.emit(`import { Client } from '@elastic/elasticsearch';`);
|
|
2121
|
+
this.emitRaw('');
|
|
2122
|
+
this.emit(`const __esClient = new Client({ node: ${host}, auth: { apiKey: ${apiKey} } });`);
|
|
2123
|
+
this.emitRaw('');
|
|
2124
|
+
this.emit(`const search = {`);
|
|
2125
|
+
this.indent++;
|
|
2126
|
+
this.emit(`async query(q, opts = {}) { return __esClient.search({ index: ${JSON.stringify(index)}, query: { match: { _all: q } }, ...opts }); },`);
|
|
2127
|
+
this.emit(`async add(doc) { return __esClient.index({ index: ${JSON.stringify(index)}, body: doc }); },`);
|
|
2128
|
+
this.emit(`async remove(id) { return __esClient.delete({ index: ${JSON.stringify(index)}, id }); },`);
|
|
2129
|
+
this.indent--;
|
|
2130
|
+
this.emit(`};`);
|
|
2131
|
+
}
|
|
2132
|
+
this.emitRaw('');
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
// ===== Image Processing =====
|
|
2136
|
+
|
|
2137
|
+
visitImage(node) {
|
|
2138
|
+
const input = this.expr(node.input);
|
|
2139
|
+
const output = node.output ? this.expr(node.output) : input;
|
|
2140
|
+
|
|
2141
|
+
this.emit(`import sharp from 'sharp';`);
|
|
2142
|
+
this.emitRaw('');
|
|
2143
|
+
this.emit(`let __img = sharp(${input});`);
|
|
2144
|
+
|
|
2145
|
+
for (const op of node.operations) {
|
|
2146
|
+
if (op.op === 'resize') {
|
|
2147
|
+
const w = op.args[0] || 800;
|
|
2148
|
+
const h = op.args[1] || null;
|
|
2149
|
+
this.emit(`__img = __img.resize(${w}${h ? ', ' + h : ''});`);
|
|
2150
|
+
} else if (op.op === 'crop') {
|
|
2151
|
+
const l = op.args[0] || 0, t = op.args[1] || 0, w = op.args[2] || 100, h = op.args[3] || 100;
|
|
2152
|
+
this.emit(`__img = __img.extract({ left: ${l}, top: ${t}, width: ${w}, height: ${h} });`);
|
|
2153
|
+
} else if (op.op === 'watermark') {
|
|
2154
|
+
const wm = op.args[0] ? (typeof op.args[0] === 'object' ? this.rawPageString(op.args[0]) : op.args[0]) : 'watermark.png';
|
|
2155
|
+
this.emit(`__img = __img.composite([{ input: ${JSON.stringify(wm)}, gravity: 'southeast' }]);`);
|
|
2156
|
+
} else if (op.op === 'rotate') {
|
|
2157
|
+
this.emit(`__img = __img.rotate(${op.args[0] || 90});`);
|
|
2158
|
+
} else if (op.op === 'blur') {
|
|
2159
|
+
this.emit(`__img = __img.blur(${op.args[0] || 5});`);
|
|
2160
|
+
} else if (op.op === 'grayscale' || op.op === 'greyscale') {
|
|
2161
|
+
this.emit(`__img = __img.grayscale();`);
|
|
2162
|
+
} else if (op.op === 'flip') {
|
|
2163
|
+
this.emit(`__img = __img.flip();`);
|
|
2164
|
+
} else if (op.op === 'format') {
|
|
2165
|
+
const fmt = op.args[0] ? (typeof op.args[0] === 'object' ? this.rawPageString(op.args[0]) : op.args[0]) : 'png';
|
|
2166
|
+
this.emit(`__img = __img.toFormat(${JSON.stringify(fmt)});`);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
this.emit(`await __img.toFile(${output});`);
|
|
2171
|
+
this.emit(`console.log('Processed:', ${output});`);
|
|
2172
|
+
this.emitRaw('');
|
|
2173
|
+
}
|
|
2065
2174
|
}
|
package/src/parser.js
CHANGED
|
@@ -113,7 +113,8 @@ export class Parser {
|
|
|
113
113
|
type === T.PAGE || type === T.CLI_APP || type === T.MAIL ||
|
|
114
114
|
type === T.GRAPHQL || type === T.DESKTOP || type === T.SCREEN ||
|
|
115
115
|
type === T.OAUTH || type === T.PAY || type === T.STORAGE ||
|
|
116
|
-
type === T.PDF || type === T.I18N
|
|
116
|
+
type === T.PDF || type === T.I18N ||
|
|
117
|
+
type === T.PUSH || type === T.SEARCH || type === T.IMAGE;
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
expectPropertyName() {
|
|
@@ -180,6 +181,9 @@ export class Parser {
|
|
|
180
181
|
case T.STORAGE: return this.parseStorage();
|
|
181
182
|
case T.PDF: return this.parsePdf();
|
|
182
183
|
case T.I18N: return this.parseI18n();
|
|
184
|
+
case T.PUSH: return this.parsePush();
|
|
185
|
+
case T.SEARCH: return this.parseSearch();
|
|
186
|
+
case T.IMAGE: return this.parseImage();
|
|
183
187
|
case T.MODEL: return this.parseModel();
|
|
184
188
|
case T.ON: return this.parseOn();
|
|
185
189
|
case T.LOG: return this.parseLog();
|
|
@@ -1156,6 +1160,7 @@ export class Parser {
|
|
|
1156
1160
|
case T.PAGE: case T.CLI_APP: case T.MAIL:
|
|
1157
1161
|
case T.GRAPHQL: case T.DESKTOP: case T.SCREEN:
|
|
1158
1162
|
case T.OAUTH: case T.PAY: case T.STORAGE: case T.PDF: case T.I18N:
|
|
1163
|
+
case T.PUSH: case T.SEARCH: case T.IMAGE:
|
|
1159
1164
|
case T.FROM: case T.AS: case T.IN:
|
|
1160
1165
|
this.advance();
|
|
1161
1166
|
return new ASTNode('Identifier', { name: tok.value });
|
|
@@ -2068,4 +2073,79 @@ export class Parser {
|
|
|
2068
2073
|
if (this.at(T.DEDENT)) this.advance();
|
|
2069
2074
|
return new ASTNode('I18nDecl', { dir, defaultLang, langs });
|
|
2070
2075
|
}
|
|
2076
|
+
|
|
2077
|
+
// push "vapid_public" "vapid_private":
|
|
2078
|
+
// endpoint "/subscribe"
|
|
2079
|
+
parsePush() {
|
|
2080
|
+
this.expect(T.PUSH);
|
|
2081
|
+
const publicKey = this.parseExpression();
|
|
2082
|
+
const privateKey = this.parseExpression();
|
|
2083
|
+
this.expect(T.COLON);
|
|
2084
|
+
this.skipNewlines();
|
|
2085
|
+
this.expect(T.INDENT);
|
|
2086
|
+
let endpoint = null;
|
|
2087
|
+
while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
|
|
2088
|
+
this.skipNewlines();
|
|
2089
|
+
if (this.at(T.DEDENT) || this.at(T.EOF)) break;
|
|
2090
|
+
const kw = this.peek().value;
|
|
2091
|
+
if (kw === 'endpoint') { this.advance(); endpoint = this.parseString(); }
|
|
2092
|
+
else { this.advance(); }
|
|
2093
|
+
this.skipNewlines();
|
|
2094
|
+
}
|
|
2095
|
+
if (this.at(T.DEDENT)) this.advance();
|
|
2096
|
+
return new ASTNode('PushDecl', { publicKey, privateKey, endpoint });
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
// search "meilisearch" "http://localhost:7700" apiKey:
|
|
2100
|
+
// index "products"
|
|
2101
|
+
parseSearch() {
|
|
2102
|
+
this.expect(T.SEARCH);
|
|
2103
|
+
const engine = this.parseString();
|
|
2104
|
+
const host = this.parseExpression();
|
|
2105
|
+
const apiKey = this.parseExpression();
|
|
2106
|
+
this.expect(T.COLON);
|
|
2107
|
+
this.skipNewlines();
|
|
2108
|
+
this.expect(T.INDENT);
|
|
2109
|
+
let index = null;
|
|
2110
|
+
while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
|
|
2111
|
+
this.skipNewlines();
|
|
2112
|
+
if (this.at(T.DEDENT) || this.at(T.EOF)) break;
|
|
2113
|
+
const kw = this.peek().value;
|
|
2114
|
+
if (kw === 'index') { this.advance(); index = this.parseString(); }
|
|
2115
|
+
else { this.advance(); }
|
|
2116
|
+
this.skipNewlines();
|
|
2117
|
+
}
|
|
2118
|
+
if (this.at(T.DEDENT)) this.advance();
|
|
2119
|
+
return new ASTNode('SearchDecl', { engine, host, apiKey, index });
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
// image "input.jpg" -> "output.jpg":
|
|
2123
|
+
// resize 800 600
|
|
2124
|
+
// crop 100 100 400 300
|
|
2125
|
+
// watermark "logo.png"
|
|
2126
|
+
parseImage() {
|
|
2127
|
+
this.expect(T.IMAGE);
|
|
2128
|
+
const input = this.parseExpression();
|
|
2129
|
+
let output = null;
|
|
2130
|
+
if (this.at(T.ARROW)) { this.advance(); output = this.parseExpression(); }
|
|
2131
|
+
this.expect(T.COLON);
|
|
2132
|
+
this.skipNewlines();
|
|
2133
|
+
this.expect(T.INDENT);
|
|
2134
|
+
const operations = [];
|
|
2135
|
+
while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
|
|
2136
|
+
this.skipNewlines();
|
|
2137
|
+
if (this.at(T.DEDENT) || this.at(T.EOF)) break;
|
|
2138
|
+
const op = this.advance().value;
|
|
2139
|
+
const args = [];
|
|
2140
|
+
while (!this.at(T.NEWLINE) && !this.at(T.DEDENT) && !this.at(T.EOF)) {
|
|
2141
|
+
if (this.at(T.STRING)) { args.push(this.parseString()); }
|
|
2142
|
+
else if (this.at(T.NUMBER)) { args.push(this.advance().value); }
|
|
2143
|
+
else { args.push(this.parseExpression()); break; }
|
|
2144
|
+
}
|
|
2145
|
+
operations.push({ op, args });
|
|
2146
|
+
this.skipNewlines();
|
|
2147
|
+
}
|
|
2148
|
+
if (this.at(T.DEDENT)) this.advance();
|
|
2149
|
+
return new ASTNode('ImageDecl', { input, output, operations });
|
|
2150
|
+
}
|
|
2071
2151
|
}
|
package/src/tokens.js
CHANGED
|
@@ -98,6 +98,9 @@ export const T = {
|
|
|
98
98
|
STORAGE: 'STORAGE',
|
|
99
99
|
PDF: 'PDF',
|
|
100
100
|
I18N: 'I18N',
|
|
101
|
+
PUSH: 'PUSH',
|
|
102
|
+
SEARCH: 'SEARCH',
|
|
103
|
+
IMAGE: 'IMAGE',
|
|
101
104
|
|
|
102
105
|
// Operators
|
|
103
106
|
ASSIGN: 'ASSIGN',
|
|
@@ -224,6 +227,9 @@ export const KEYWORDS = {
|
|
|
224
227
|
'storage': T.STORAGE,
|
|
225
228
|
'pdf': T.PDF,
|
|
226
229
|
'i18n': T.I18N,
|
|
230
|
+
'push': T.PUSH,
|
|
231
|
+
'search': T.SEARCH,
|
|
232
|
+
'image': T.IMAGE,
|
|
227
233
|
'true': T.BOOL,
|
|
228
234
|
'false': T.BOOL,
|
|
229
235
|
'null': T.NULL,
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"name": "keyword.control.naide"
|
|
88
88
|
},
|
|
89
89
|
"keywords-server": {
|
|
90
|
-
"match": "\\b(server|bot|slash|get|post|put|del|patch|mid|cors|limit|auth|crud|static|ws|sse|group|upload|cookie|session|view|cache|validate|openapi|error|on|page|cli|mail|graphql|desktop|screen|oauth|pay|storage|pdf|i18n)\\b",
|
|
90
|
+
"match": "\\b(server|bot|slash|get|post|put|del|patch|mid|cors|limit|auth|crud|static|ws|sse|group|upload|cookie|session|view|cache|validate|openapi|error|on|page|cli|mail|graphql|desktop|screen|oauth|pay|storage|pdf|i18n|push|search|image)\\b",
|
|
91
91
|
"name": "keyword.other.naide"
|
|
92
92
|
},
|
|
93
93
|
"keywords-declaration": {
|