@makano/rew 1.1.2 → 1.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,8 +6,9 @@ const { hideBin } = require('yargs/helpers');
6
6
  const { fork, exec } = require('child_process');
7
7
  const { watch } = require('chokidar');
8
8
  const utils = require('./utils');
9
- const { existsSync } = require('fs');
9
+ const { existsSync, readFileSync, writeFileSync, mkdirSync } = require('fs');
10
10
  const { log } = require('./log');
11
+ const { compileFile, compile } = require('../modules/compiler');
11
12
 
12
13
  yargs(hideBin(process.argv))
13
14
  .command(
@@ -125,9 +126,69 @@ yargs(hideBin(process.argv))
125
126
  .positional('file', {
126
127
  describe: 'File to build',
127
128
  type: 'string',
129
+ })
130
+ .option('output', {
131
+ alias: 'o',
132
+ describe: 'Output directory',
133
+ type: 'string',
128
134
  });
129
135
  }, (argv) => {
130
- console.log(`Building file: ${argv.file}`);
136
+
137
+ function readFile(filePath) {
138
+ return readFileSync(filePath, { encoding: 'utf-8' });
139
+ }
140
+
141
+ function extractImports(content) {
142
+ const importRegex = /(\w+)\s*=\s*imp\s*['"](.+?)['"]/g;
143
+ const imports = [];
144
+ let match;
145
+ while ((match = importRegex.exec(content)) !== null) {
146
+ imports.push({ variable: match[1], url: match[2] });
147
+ }
148
+ return imports;
149
+ }
150
+
151
+ function writeCompiledFile(filePath, compiledCode) {
152
+ const dirName = outputDir ? outputDir : path.dirname(filePath);
153
+ if(!existsSync(dirName)) mkdirSync(dirName, { recursive: true });
154
+ const baseName = path.basename(filePath, path.extname(filePath));
155
+ const newFilePath = path.join(dirName, `${baseName}.js`);
156
+ writeFileSync(newFilePath, compiledCode, { encoding: 'utf-8' });
157
+ log(`Compiled: ${newFilePath}`);
158
+ }
159
+
160
+ function processFile(filePath, importsArray) {
161
+ const content = readFile(filePath);
162
+ const imports = extractImports(content);
163
+
164
+ imports.forEach(importStatement => {
165
+ const importedFilePath = path.resolve(path.dirname(filePath), importStatement.url);
166
+ if (!importsArray.some(importObj => importObj.url === importStatement.url)) {
167
+
168
+ if(existsSync(importedFilePath)){
169
+ importsArray.push(importStatement);
170
+ processFile(importedFilePath, importsArray);
171
+ } else if(existsSync(importedFilePath+'.coffee')){
172
+ importsArray.push(importStatement);
173
+ processFile(importedFilePath+'.coffee', importsArray);
174
+ } else if(existsSync(importedFilePath+'.js')){
175
+ importsArray.push(importStatement);
176
+ processFile(importedFilePath+'.js', importsArray);
177
+ }
178
+
179
+ }
180
+ });
181
+
182
+ const compiled = compile({ content }, {});
183
+ writeCompiledFile(filePath, compiled);
184
+ }
185
+
186
+ const filePath = path.resolve(process.cwd(), argv.file);
187
+ const importsArray = [];
188
+ const outputDir = argv.output ? path.resolve(process.cwd(), argv.output) : null;
189
+ log('Start compile at', outputDir || 'default path');
190
+ processFile(filePath, importsArray);
191
+ log('Compiled', importsArray.length + 1, 'files.', ':end');
131
192
  })
132
193
  .help()
133
194
  .argv;
@@ -0,0 +1,27 @@
1
+ const shell = require('child_process');
2
+
3
+
4
+ module.exports = (currentFile) => {
5
+
6
+ function exec(command, options){
7
+ return shell.execSync(command, { stdio: options?.output == false ? null : 'inherit' });
8
+ }
9
+
10
+ exec.background = function execAsync(command, options, callback){
11
+ if(typeof options == "function" && !callback){
12
+ callback = options;
13
+ options = {};
14
+ }
15
+ if(!options) options = {};
16
+ if(!callback) callback = () => {};
17
+ return shell.exec(command, {
18
+ ...options,
19
+ }, callback);
20
+ }
21
+
22
+ function spawn(command, options){
23
+ return shell.spawn(command, options);
24
+ }
25
+
26
+ return { exec, spawn };
27
+ }
@@ -1,9 +1,27 @@
1
+
2
+ function exportsThe(item, name, context){
3
+ if (name) {
4
+ if(!context.module.exports) context.module.exports = {};
5
+ context.module.exports[name] = item;
6
+ } else {
7
+ if(context.module.exports) context.module.exports.default = item;
8
+ else context.module.exports = item;
9
+ }
10
+ }
11
+
12
+ module.exports.pubFunction = function (context) {
13
+ return function (name, item) {
14
+ if(name && !item){
15
+ item = name;
16
+ name = null;
17
+ }
18
+ exportsThe(item, name, context);
19
+ };
20
+ };
21
+
1
22
  module.exports.exportsFunction = function (context) {
2
23
  return function (item, name) {
3
- if (name) {
4
- context.module.exports[name] = item;
5
- } else {
6
- context.module.exports = item;
7
- }
24
+ exportsThe(item, name, context);
8
25
  };
9
26
  };
27
+
@@ -18,7 +18,7 @@ module.exports = (currentFile) => {
18
18
  }
19
19
 
20
20
  function exists(filepath, options){
21
- return fs.existsSync(filepath);
21
+ return fs.existsSync(gp(filepath));
22
22
  }
23
23
 
24
24
  function fstat(filepath, options){
@@ -60,7 +60,7 @@ module.exports.imp = function (runPath, context) {
60
60
 
61
61
  const exec = (coptions = {}) => runPath(
62
62
  filepath,
63
- { ...options, useContext: execOptions.sharedContext == false ? false : true, ...coptions },
63
+ { import: options, main: false, useContext: execOptions.sharedContext == false ? false : true, ...coptions },
64
64
  execOptions.sharedContext == false ? {} : context,
65
65
  ).context.module.exports;
66
66
 
@@ -1,8 +1,160 @@
1
1
  const { compile } = require("../../coffeescript/coffeescript");
2
2
  const { getFile } = require("./fs");
3
3
 
4
+ function tokenizeCoffeeScript(code) {
5
+ const tokens = [];
6
+ let currentToken = '';
7
+
8
+ for (let i = 0; i < code.length; i++) {
9
+ const char = code[i];
10
+ const nextChar = code[i + 1];
11
+
12
+ if (char === '#') {
13
+ // Comment
14
+ tokens.push({ type: 'COMMENT', value: char + code.substring(i + 1).split('\n')[0] });
15
+ i = code.indexOf('\n', i);
16
+ } else if (char === '"' || char === "'") {
17
+ // String
18
+ let string = char;
19
+ let escaped = false;
20
+ i++;
21
+ while (i < code.length && (code[i] !== char || escaped)) {
22
+ string += code[i];
23
+ if (code[i] === '\\' && !escaped) {
24
+ escaped = true;
25
+ } else {
26
+ escaped = false;
27
+ }
28
+ i++;
29
+ }
30
+ string += char; // Include closing quote
31
+ tokens.push({ type: 'STRING', value: string });
32
+ } else if (char === '/' && (nextChar === '/' || nextChar === '*')) {
33
+ // Regular expression
34
+ let regex = char;
35
+ i++;
36
+ while (i < code.length && (code[i] !== '/' || regex.endsWith('\\'))) {
37
+ regex += code[i];
38
+ i++;
39
+ }
40
+ regex += '/';
41
+ tokens.push({ type: 'REGEX', value: regex });
42
+ } else if (/\s/.test(char)) {
43
+ // Whitespace
44
+ if(tokens[tokens.length-1]?.type == 'WHITESPACE'
45
+ && tokens[tokens.length-1].value[0] == char
46
+ ){
47
+ tokens[tokens.length-1].value += char;
48
+ } else {
49
+ tokens.push({ type: 'WHITESPACE', value: char });
50
+ }
51
+ } else if (/[a-zA-Z_$]/.test(char)) {
52
+ // Identifier
53
+ let identifier = char;
54
+ i++;
55
+ while (i < code.length && /[a-zA-Z0-9_$]/.test(code[i])) {
56
+ identifier += code[i];
57
+ i++;
58
+ }
59
+ tokens.push({ type: 'IDENTIFIER', value: identifier });
60
+ i--; // Move back one character to recheck
61
+ } else {
62
+ // Other characters
63
+ tokens.push({ type: 'OTHER', value: char });
64
+ }
65
+ }
66
+
67
+ return tokens;
68
+ }
69
+
70
+ const gnextToken = (i, n, tokens) => {
71
+ return tokens[i + n] ? tokens[i + n].type == 'WHITESPACE' ? gnextToken(i, n + 1, tokens) : { nextToken: tokens[i + n], n } : null;
72
+ }
73
+
74
+ const fnextToken = (i, tokens, type, value) => {
75
+ return tokens.map((t, ind) => { t.ti = ind; return t }).slice(i, tokens.length - 1).map((t, ind) => { t.ri = ind; t.index = ind - i; return t }).find(t => t.type == type && (value ? t.value == value : true));
76
+ }
77
+
78
+ function compileRewStuff(content) {
79
+ const tokens = tokenizeCoffeeScript(content);
80
+ let result = '';
81
+
82
+ let hooks = [];
83
+
84
+ for (let i = 0; i < tokens.length; i++) {
85
+ const token = tokens[i];
86
+ let { nextToken, n } = gnextToken(i, 1, tokens) || {};
87
+
88
+ if (token.type === 'IDENTIFIER' && token.value === 'import') {
89
+ // console.log(nextToken.type);
90
+ let ind = i + n + 2;
91
+
92
+ let defaultName;
93
+ if (nextToken.value === '{') {
94
+ const closingBraceToken = fnextToken(ind, tokens, 'OTHER', '}');
95
+ const nameToken = fnextToken(ind, tokens, 'STRING');
96
+ if (closingBraceToken) {
97
+ const exportsTokens = tokens.slice(ind, closingBraceToken.ti);
98
+ const exports = exportsTokens.filter(t => t.type === 'IDENTIFIER').map(t => t.value).join(', ');
99
+ result += `{ ${exports} } = inc ${nameToken.value}`;
100
+ i = nameToken.ti;
101
+ }
102
+ } else if (nextToken.value === '*') {
103
+ const asToken = fnextToken(ind, tokens, 'IDENTIFIER', 'as');
104
+ const nameToken = fnextToken(asToken.ri, tokens, 'STRING');
105
+ if (asToken) {
106
+ const nextToken = fnextToken(asToken.ti + 1, tokens, 'IDENTIFIER');
107
+ defaultName = nextToken.value;
108
+ result += `${defaultName} = inc ${nameToken.value}`;
109
+ i = ind + 6;
110
+ }
111
+ } else {
112
+ const nameToken = fnextToken(ind, tokens, 'STRING');
113
+ defaultName = nextToken.value;
114
+ result += `{ default: ${defaultName} } = inc ${nameToken.value}`;
115
+ i = ind + 2;
116
+ }
117
+
118
+ const nextLastToken = fnextToken(i, tokens, 'IDENTIFIER');
119
+
120
+ if(nextLastToken?.value == 'assert'){
121
+ result += ', ';
122
+ i += 3;
123
+ }
124
+
125
+ continue;
126
+ }
127
+
128
+
129
+ if (token.type === 'IDENTIFIER' && token.value === 'pub' &&
130
+ nextToken && nextToken.type === 'IDENTIFIER' &&
131
+ nextToken.value && nextToken.value !== 'undefined') {
132
+
133
+ hooks.push({
134
+ index: i + 1,
135
+ value: `"${nextToken.value}", `
136
+ });
137
+ }
138
+
139
+ result += token.value;
140
+ if (hooks.length) {
141
+ hooks.forEach((hook, ind) => {
142
+ if (i == hook.index) {
143
+ result += hook.value;
144
+ hooks.splice(ind, 1);
145
+ }
146
+ });
147
+ }
148
+ }
149
+
150
+ // console.log(result)
151
+
152
+ return result;
153
+ }
154
+
155
+
4
156
  const cpl = (module.exports.compile = function (file, options = {}) {
5
- return compile(file.content, options);
157
+ return compile(compileRewStuff(file.content), { ...options, filename: file.path, bare: true, inlineMap: true });
6
158
  });
7
159
 
8
160
  module.exports.compileFile = function (filepath, options = {}) {
@@ -1,11 +1,12 @@
1
1
  const defaultContext = require("../const/default");
2
2
  const { execOptions } = require("../const/opt");
3
3
  const emitter = require("../functions/emitter");
4
- const { exportsFunction } = require("../functions/export");
4
+ const { exportsFunction, pubFunction } = require("../functions/export");
5
5
  const { imp } = require("../functions/import");
6
6
  const { customRequire } = require("../functions/require");
7
7
  const fsLib = require('../functions/fs');
8
8
  const pathLib = require('../functions/path');
9
+ const execLib = require('../functions/exec');
9
10
 
10
11
  module.exports.prepareContext = function (
11
12
  custom_context,
@@ -16,11 +17,15 @@ module.exports.prepareContext = function (
16
17
  let context = {
17
18
  module: {
18
19
  exports: null,
19
- options,
20
20
  filepath,
21
+ main: options.main ?? true,
21
22
  imports: []
22
23
  },
23
- ...fsLib(filepath)
24
+ imports: {
25
+ meta: {},
26
+ assert: options.import ?? {}
27
+ },
28
+ ...fsLib(filepath),
24
29
  };
25
30
  if (options.useContext) {
26
31
  context = {
@@ -32,6 +37,7 @@ module.exports.prepareContext = function (
32
37
  ...context,
33
38
  ...defaultContext,
34
39
  ...pathLib(filepath),
40
+ ...execLib(filepath),
35
41
  require: (package) => {
36
42
  try {
37
43
  return execOptions.nativeRequire || package.startsWith('node:') ? require(package.startsWith('node:') ? package.split('node:')[1] : package) : customRequire(package, filepath);
@@ -48,6 +54,14 @@ module.exports.prepareContext = function (
48
54
  ...custom_context,
49
55
  };
50
56
  context.imp = imp(runPath, context);
57
+ context.inc = (package, asserts) => {
58
+ try{
59
+ return context.imp(package, asserts);
60
+ } catch(e) {
61
+ return context.require(package);
62
+ }
63
+ };
64
+ context.pub = pubFunction(context);
51
65
  context.exports = exportsFunction(context);
52
66
  }
53
67
  if (!context.global) context.global = context;
@@ -59,6 +73,6 @@ module.exports.prepareContext = function (
59
73
  cwd: () => process.cwd(),
60
74
  arch: process.arch
61
75
  };
62
- // console.log(custom_context);
76
+ context.imports.assert = options.import ?? {};
63
77
  return context;
64
78
  };
@@ -22,7 +22,7 @@ parentPort.on('message', (data) => {
22
22
  exec(`(${workerData.cb}).call({ process }, context)`, { print: (...a) => console.log(...a), process: {
23
23
  on: (e, cb) => { target.on(e, cb) },
24
24
  off: (e) => { target.off(e) },
25
- emit: (e) => { parentPort.postMessage({ type: 'event', event: e, data }) },
25
+ emit: (e, data) => { parentPort.postMessage({ type: 'event', event: e, data }) },
26
26
  exit: (code) => process.exit(code),
27
27
  finish: (data) => {
28
28
  parentPort.postMessage({ type: 'result', result: data });
@@ -44,7 +44,10 @@ module.exports = (context) => ({
44
44
  stop();
45
45
  });
46
46
 
47
+ let exiting = false;
48
+
47
49
  worker.on('exit', (code) => {
50
+ if(exiting) return;
48
51
  stop();
49
52
  if (code !== 0) {
50
53
  throw new Error(`Worker stopped with exit code ${code}`);
@@ -57,7 +60,8 @@ module.exports = (context) => ({
57
60
  on: (e, cb) => listener.on(e, cb),
58
61
  off: (e, cb) => listener.off(e, cb),
59
62
  emit: (e, data) => worker?.postMessage({ type: 'event', event: e, data }),
60
- get: () => dataResult.wait()
63
+ get: () => dataResult.wait(),
64
+ stop: () => { exiting = true; stop(); }
61
65
  };
62
66
  }
63
67
  };
@@ -26,7 +26,7 @@ const defaultOptions = {
26
26
  onExit: () => process.exit(),
27
27
  style: '',
28
28
  stylePath: THEME_PATH,
29
- exec: () => {},
29
+ exec: () => { },
30
30
  execContext: {}
31
31
  };
32
32
 
@@ -44,18 +44,22 @@ module.exports = (context) => ({
44
44
 
45
45
  options.runId = runId;
46
46
 
47
- if(fs.existsSync(options.stylePath)) options.style = fs.readFileSync(options.stylePath, { encoding: 'utf-8' }) + '\n' + options.style;
47
+ if (fs.existsSync(options.stylePath)) options.style = fs.readFileSync(options.stylePath, { encoding: 'utf-8' }) + '\n' + options.style;
48
48
 
49
- options.style = ' */\n'+options.style+'\n/* ';
49
+ options.style = ' */\n' + options.style + '\n/* ';
50
50
 
51
51
  const HTML = replaceString(HTML_STRING, options);
52
52
  const JS = replaceString(JS_STRING, options);
53
53
 
54
+ /**
55
+ * Queue for future writes
56
+ * @type {string[]}
57
+ * */
54
58
  const queue = [];
55
59
 
56
60
  const send = (data) => {
57
61
  const content = fs.readFileSync(tmpFile, { encoding: 'utf-8' });
58
- if(content) {
62
+ if (content) {
59
63
  queue.push(data);
60
64
  } else {
61
65
  fs.writeFileSync(tmpFile, typeof data !== "string" ? JSON.stringify(data) : data);
@@ -77,20 +81,20 @@ module.exports = (context) => ({
77
81
  output: process.stdout
78
82
  });
79
83
 
80
- rl.question('', () => {});
84
+ rl.question('', () => { });
81
85
 
82
86
  fs.writeFileSync(tmpFile, '');
83
87
 
84
88
  fs.watch(tmpFile, { encoding: 'utf-8' })
85
- .on('change', () => {
86
- if(queue.length){
87
- send(queue.pop());
88
- }
89
- });
89
+ .on('change', () => {
90
+ if (queue.length) {
91
+ send(queue.pop());
92
+ }
93
+ });
90
94
 
91
95
  const p = spawn(BIN_PATH, [runId]);
92
96
 
93
-
97
+
94
98
 
95
99
  p.on("close", (code) => {
96
100
  rl.close();
@@ -103,12 +107,12 @@ module.exports = (context) => ({
103
107
  });
104
108
 
105
109
  g_emitter.on('recieve', (edata) => {
106
- if(edata.action.startsWith('hook:')){
110
+ if (edata.action.startsWith('hook:')) {
107
111
  const hook = hookedSocketListeners[edata.data.rid];
108
112
  const type = edata.action.split('hook:')[1];
109
- if(hook && hook.type == type) {
113
+ if (hook && hook.type == type) {
110
114
  hookedSocketListeners[edata.data.rid].cb(edata.data.object);
111
- if(hook.once) delete hookedSocketListeners[edata.data.rid];
115
+ if (hook.once) delete hookedSocketListeners[edata.data.rid];
112
116
  }
113
117
  }
114
118
  });
@@ -119,7 +123,7 @@ module.exports = (context) => ({
119
123
  const d = data.toString().split("RESPONSE::")[1];
120
124
  const jd = JSON.parse(d);
121
125
  recieve(jd);
122
- } else if(data.toString().trim().endsWith('SETUP::READY')) {
126
+ } else if (data.toString().trim().endsWith('SETUP::READY')) {
123
127
  console.log('READY');
124
128
  r(uiClasses(context, options, sendEvent, (cb) => {
125
129
  g_emitter.on('recieve', cb);
@@ -128,15 +132,15 @@ module.exports = (context) => ({
128
132
  }, (rid) => { // Remove hook
129
133
  delete hookedSocketListeners[rid];
130
134
  }));
131
- } else if(data.toString().endsWith('SETUP::HTML')) {
132
- send({action: 'JS2', data: JS, isSetup: true});
133
- } else if(data.toString() == 'INIT::READY') {
134
- send({action: 'HTML', data: HTML});
135
+ } else if (data.toString().endsWith('SETUP::HTML')) {
136
+ send({ action: 'JS2', data: JS, isSetup: true });
137
+ } else if (data.toString() == 'INIT::READY') {
138
+ send({ action: 'HTML', data: HTML });
135
139
  } else {
136
140
  console.log(data.toString());
137
141
  }
138
142
  });
139
-
143
+
140
144
  p.stderr.on("data", (data) => {
141
145
  console.error(data.toString());
142
146
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makano/rew",
3
- "version": "1.1.2",
3
+ "version": "1.1.5",
4
4
  "description": "A simple coffescript runtime",
5
5
  "main": "lib/rew/main.js",
6
6
  "directories": {