fix: core template engine and upload params to resolve Syntax error

This commit is contained in:
yczpf2019
2026-01-24 19:37:52 +08:00
parent ec4814c208
commit ef98c5e89f
4 changed files with 313 additions and 331 deletions

View File

@@ -1,21 +0,0 @@
import os
import json
boards_dir = r"D:\DE-MIXLY\mixly3-server\mixly\boards"
for root, dirs, files in os.walk(boards_dir):
for file in files:
if file == "config.json":
file_path = os.path.join(root, file)
try:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
new_content = content.replace('\\"{esptool}\\"', '{esptool}').replace('\\"{ampy}\\"', '{ampy}')
if content != new_content:
print(f"Fixing: {file_path}")
with open(file_path, "w", encoding="utf-8") as f:
f.write(new_content)
except Exception as e:
print(f"Error processing {file_path}: {e}")

View File

@@ -1,350 +1,353 @@
goog.loadJs('web', () => { goog.loadJs('web', () => {
goog.require('path'); goog.require('path');
goog.require('layui'); goog.require('layui');
goog.require('dayjs.duration'); goog.require('dayjs.duration');
goog.require('Mixly.Debug'); goog.require('Mixly.Debug');
goog.require('Mixly.LayerExt'); goog.require('Mixly.LayerExt');
goog.require('Mixly.Msg'); goog.require('Mixly.Msg');
goog.require('Mixly.Env'); goog.require('Mixly.Env');
goog.require('Mixly.Config'); goog.require('Mixly.Config');
goog.require('Mixly.Workspace'); goog.require('Mixly.Workspace');
goog.require('Mixly.MString'); goog.require('Mixly.MString');
goog.require('Mixly.LayerProgress'); goog.require('Mixly.LayerProgress');
goog.require('Mixly.WebSocket.Serial'); goog.require('Mixly.WebSocket.Serial');
goog.provide('Mixly.WebSocket.BU'); goog.provide('Mixly.WebSocket.BU');
const { const {
Debug, Debug,
LayerExt, LayerExt,
Config, Config,
Msg, Msg,
Env, Env,
Workspace, Workspace,
MString, MString,
LayerProgress, LayerProgress,
WebSocket WebSocket
} = Mixly; } = Mixly;
const { SELECTED_BOARD } = Config; const { SELECTED_BOARD } = Config;
const { Serial } = WebSocket; const { Serial } = WebSocket;
const { layer } = layui; const { layer } = layui;
class WebSocketBU { class WebSocketBU {
static { static {
this.mixlySocket = null; this.mixlySocket = null;
this.socket = null; this.socket = null;
this.shell = null; this.shell = null;
this.getSocket = function () { this.getSocket = function () {
return this.socket; return this.socket;
} }
this.getMixlySocket = function () { this.getMixlySocket = function () {
return this.mixlySocket; return this.mixlySocket;
} }
this.init = function (mixlySocket) { this.init = function (mixlySocket) {
this.mixlySocket = mixlySocket; this.mixlySocket = mixlySocket;
this.socket = mixlySocket.getSocket(); this.socket = mixlySocket.getSocket();
this.shell = new WebSocketBU(); this.shell = new WebSocketBU();
const socket = this.socket; const socket = this.socket;
socket.on('micropython.dataEvent', (data) => { socket.on('micropython.dataEvent', (data) => {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.addValue(data);
});
socket.on('micropython.errorEvent', (data) => {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.addValue(data);
});
}
this.initBurn = function () {
if (!this.mixlySocket.isConnected()) {
layer.msg(Msg.Lang['websocket.offline'], { time: 1000 });
return;
}
const port = Serial.getSelectedPortName();
if (!port) {
layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
time: 1000
});
return;
}
const { mainStatusBarTabs } = Mixly; const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.addValue(data); mainStatusBarTabs.changeTo('output');
}); mainStatusBarTabs.show();
statusBarTerminal.setValue(`${Msg.Lang['shell.burning']}...\n`);
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
const closePromise = statusBarSerial ? statusBarSerial.close() : Promise.resolve();
closePromise
.then(() => {
return this.shell.burn(port);
})
.then((info) => {
this.endCallback(info.code, info.time);
})
.catch((error) => {
Debug.error(error);
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.burnFailed']}==\n`);
});
}
socket.on('micropython.errorEvent', (data) => { this.initUpload = function () {
if (!this.mixlySocket.isConnected()) {
layer.msg(Msg.Lang['websocket.offline'], { time: 1000 });
return;
}
const port = Serial.getSelectedPortName();
if (!port) {
layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
time: 1000
});
return;
}
const { mainStatusBarTabs } = Mixly; const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.addValue(data); mainStatusBarTabs.changeTo('output');
mainStatusBarTabs.show();
statusBarTerminal.setValue(`${Msg.Lang['shell.uploading']}...\n`);
const mainWorkspace = Workspace.getMain();
const editor = mainWorkspace.getEditorsManager().getActive();
const code = editor.getCode();
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
const closePromise = statusBarSerial ? statusBarSerial.close() : Promise.resolve();
closePromise
.then(() => {
return this.shell.upload(port, code)
})
.then((info) => {
this.endCallback(info.code, info.time);
if (info.code || !Serial.portIsLegal(port)) {
return;
}
mainStatusBarTabs.add('serial', port);
mainStatusBarTabs.changeTo(port);
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
statusBarSerial.open()
.then(() => {
return statusBarSerial.setBaudRate(115200);
})
.catch(Debug.error);
})
.catch((error) => {
Debug.error(error);
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.uploadFailed']}==\n`);
});
}
this.endCallback = function (code, time) {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output');
let message = '';
if (code) {
message = (this.shell.isBurning() ? Msg.Lang['shell.burnFailed'] : Msg.Lang['shell.uploadFailed']);
statusBarTerminal.addValue(`\n==${message}==\n`);
} else {
message = (this.shell.isBurning() ? Msg.Lang['shell.burnSucc'] : Msg.Lang['shell.uploadSucc']);
statusBarTerminal.addValue(`\n==${message}(${Msg.Lang['shell.timeCost']} ${dayjs.duration(time).format('HH:mm:ss.SSS')
})==\n`);
}
layer.msg(message, { time: 1000 });
}
}
#running_ = false;
#upload_ = false;
#killing_ = false;
#layer_ = null;
constructor() {
this.#layer_ = new LayerProgress({
width: 200,
cancelValue: Msg.Lang['nav.btn.stop'],
skin: 'layui-anim layui-anim-scale',
cancel: () => {
if (this.#killing_) {
return false;
}
this.#layer_.title(`${Msg.Lang['shell.aborting']}...`);
this.#killing_ = true;
this.kill().catch(Debug.error);
return false;
},
cancelDisplay: false
}); });
} }
this.initBurn = function () { async burn(port) {
if (!this.mixlySocket.isConnected()) { return new Promise(async (resolve, reject) => {
layer.msg(Msg.Lang['websocket.offline'], { time: 1000 }); this.#running_ = true;
return; this.#upload_ = false;
} this.#killing_ = false;
const port = Serial.getSelectedPortName(); this.showProgress();
if (!port) { const config = {
layer.msg(Msg.Lang['statusbar.serial.noDevice'], { boardDirPath: `.${Env.boardDirPath}`,
time: 1000 port,
}); command: SELECTED_BOARD.burn.command,
return; baudrate: Boards.getSelectedBoardConfigParam('BurnSpeed'),
} reset: SELECTED_BOARD.burn.reset || []
const { mainStatusBarTabs } = Mixly; };
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); const mixlySocket = WebSocketBU.getMixlySocket();
mainStatusBarTabs.changeTo('output'); mixlySocket.emit('micropython.burn', config, (response) => {
mainStatusBarTabs.show(); this.hideProgress();
statusBarTerminal.setValue(`${Msg.Lang['shell.burning']}...\n`); if (response.error) {
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port); reject(response.error);
const closePromise = statusBarSerial ? statusBarSerial.close() : Promise.resolve();
closePromise
.then(() => {
return this.shell.burn(port);
})
.then((info) => {
this.endCallback(info.code, info.time);
})
.catch((error) => {
Debug.error(error);
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.burnFailed']}==\n`);
});
}
this.initUpload = function () {
if (!this.mixlySocket.isConnected()) {
layer.msg(Msg.Lang['websocket.offline'], { time: 1000 });
return;
}
const port = Serial.getSelectedPortName();
if (!port) {
layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
time: 1000
});
return;
}
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output');
mainStatusBarTabs.show();
statusBarTerminal.setValue(`${Msg.Lang['shell.uploading']}...\n`);
const mainWorkspace = Workspace.getMain();
const editor = mainWorkspace.getEditorsManager().getActive();
const code = editor.getCode();
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
const closePromise = statusBarSerial ? statusBarSerial.close() : Promise.resolve();
closePromise
.then(() => {
return this.shell.upload(port, code)
})
.then((info) => {
this.endCallback(info.code, info.time);
if (info.code || !Serial.portIsLegal(port)) {
return; return;
} }
mainStatusBarTabs.add('serial', port); const [error, result] = response;
mainStatusBarTabs.changeTo(port); if (error) {
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port); reject(error);
statusBarSerial.open() } else {
.then(() => { resolve(result);
return statusBarSerial.setBaudRate(115200); }
})
.catch(Debug.error);
})
.catch((error) => {
Debug.error(error);
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.uploadFailed']}==\n`);
}); });
}
this.endCallback = function (code, time) {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output');
let message = '';
if (code) {
message = (this.shell.isBurning() ? Msg.Lang['shell.burnFailed'] : Msg.Lang['shell.uploadFailed']);
statusBarTerminal.addValue(`\n==${message}==\n`);
} else {
message = (this.shell.isBurning() ? Msg.Lang['shell.burnSucc'] : Msg.Lang['shell.uploadSucc']);
statusBarTerminal.addValue(`\n==${message}(${Msg.Lang['shell.timeCost']} ${
dayjs.duration(time).format('HH:mm:ss.SSS')
})==\n`);
}
layer.msg(message, { time: 1000 });
}
}
#running_ = false;
#upload_ = false;
#killing_ = false;
#layer_ = null;
constructor() {
this.#layer_ = new LayerProgress({
width: 200,
cancelValue: Msg.Lang['nav.btn.stop'],
skin: 'layui-anim layui-anim-scale',
cancel: () => {
if (this.#killing_) {
return false;
}
this.#layer_.title(`${Msg.Lang['shell.aborting']}...`);
this.#killing_ = true;
this.kill().catch(Debug.error);
return false;
},
cancelDisplay: false
});
}
async burn(port) {
return new Promise(async (resolve, reject) => {
this.#running_ = true;
this.#upload_ = false;
this.#killing_ = false;
this.showProgress();
const config = {
boardDirPath: `.${Env.boardDirPath}`,
port,
command: SELECTED_BOARD.burn.command
};
const mixlySocket = WebSocketBU.getMixlySocket();
mixlySocket.emit('micropython.burn', config, (response) => {
this.hideProgress();
if (response.error) {
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
}); });
}); }
}
async upload(port, code) { async upload(port, code) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
this.#running_ = true; this.#running_ = true;
this.#upload_ = true; this.#upload_ = true;
this.#killing_ = false; this.#killing_ = false;
this.showProgress(); this.showProgress();
const importsMap = this.getImportModules(code); const importsMap = this.getImportModules(code);
let libraries = {}; let libraries = {};
for (let key in importsMap) { for (let key in importsMap) {
const filename = importsMap[key]['__name__']; const filename = importsMap[key]['__name__'];
const data = goog.readFileSync(importsMap[key]['__path__']); const data = goog.readFileSync(importsMap[key]['__path__']);
libraries[filename] = data; libraries[filename] = data;
} }
const config = { const config = {
boardDirPath: `.${Env.boardDirPath}`, boardDirPath: `.${Env.boardDirPath}`,
command: SELECTED_BOARD.upload.command, command: SELECTED_BOARD.upload.command,
filePath: SELECTED_BOARD.upload.filePath, filePath: SELECTED_BOARD.upload.filePath,
port, code, libraries baudrate: Boards.getSelectedBoardConfigParam('BurnSpeed'),
}; reset: SELECTED_BOARD.upload.reset || [],
port, code, libraries
};
const mixlySocket = WebSocketBU.getMixlySocket(); const mixlySocket = WebSocketBU.getMixlySocket();
mixlySocket.emit('micropython.upload', config, (response) => { mixlySocket.emit('micropython.upload', config, (response) => {
this.hideProgress(); this.hideProgress();
if (response.error) { if (response.error) {
reject(response.error); reject(response.error);
return; return;
} }
const [error, result] = response; const [error, result] = response;
if (error) { if (error) {
reject(error); reject(error);
} else { } else {
resolve(result); resolve(result);
} }
});
}); });
});
}
getImportModulesName(code) {
// 正则表达式: 匹配 import 或 from 导入语句
const importRegex = /(?:import\s+([a-zA-Z0-9_]+)|from\s+([a-zA-Z0-9_]+)\s+import)/g;
let imports = [];
let match;
while ((match = importRegex.exec(code)) !== null) {
if (match[1]) {
imports.push(match[1]); // 'import module'
}
if (match[2]) {
imports.push(match[2]); // 'from module import ...'
}
}
return imports;
}
getImportModules(code) {
let importsMap = {};
const libPath = SELECTED_BOARD.upload.libPath;
for (let i = libPath.length - 1; i >= 0; i--) {
const dirname = MString.tpl(libPath[i], { indexPath: Env.boardDirPath });
const map = goog.readJsonSync(path.join(dirname, 'map.json'));
if (!(map && map instanceof Object)) {
continue;
}
for (let key in map) {
importsMap[key] = structuredClone(map[key]);
importsMap[key]['__path__'] = path.join(dirname, map[key]['__name__']);
}
} }
let usedMap = {}; getImportModulesName(code) {
let currentImports = this.getImportModulesName(code); // 正则表达式: 匹配 import 或 from 导入语句
while (currentImports.length) { const importRegex = /(?:import\s+([a-zA-Z0-9_]+)|from\s+([a-zA-Z0-9_]+)\s+import)/g;
let temp = [];
for (let moduleName of currentImports) { let imports = [];
let moduleInfo = importsMap[moduleName]; let match;
if (!moduleInfo) { while ((match = importRegex.exec(code)) !== null) {
if (match[1]) {
imports.push(match[1]); // 'import module'
}
if (match[2]) {
imports.push(match[2]); // 'from module import ...'
}
}
return imports;
}
getImportModules(code) {
let importsMap = {};
const libPath = SELECTED_BOARD.upload.libPath;
for (let i = libPath.length - 1; i >= 0; i--) {
const dirname = MString.tpl(libPath[i], { indexPath: Env.boardDirPath });
const map = goog.readJsonSync(path.join(dirname, 'map.json'));
if (!(map && map instanceof Object)) {
continue; continue;
} }
usedMap[moduleName] = moduleInfo; for (let key in map) {
const moduleImports = moduleInfo['__require__']; importsMap[key] = structuredClone(map[key]);
if (!moduleImports) { importsMap[key]['__path__'] = path.join(dirname, map[key]['__name__']);
continue;
} }
for (let name of moduleImports) { }
if (usedMap[name] || !importsMap[name] || temp.includes(name)) {
let usedMap = {};
let currentImports = this.getImportModulesName(code);
while (currentImports.length) {
let temp = [];
for (let moduleName of currentImports) {
let moduleInfo = importsMap[moduleName];
if (!moduleInfo) {
continue; continue;
} }
temp.push(name); usedMap[moduleName] = moduleInfo;
const moduleImports = moduleInfo['__require__'];
if (!moduleImports) {
continue;
}
for (let name of moduleImports) {
if (usedMap[name] || !importsMap[name] || temp.includes(name)) {
continue;
}
temp.push(name);
}
} }
currentImports = temp;
} }
currentImports = temp; return usedMap;
} }
return usedMap;
}
async kill() { async kill() {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const mixlySocket = WebSocketBU.getMixlySocket(); const mixlySocket = WebSocketBU.getMixlySocket();
mixlySocket.emit('micropython.kill', (response) => { mixlySocket.emit('micropython.kill', (response) => {
if (response.error) { if (response.error) {
reject(response.error); reject(response.error);
return; return;
} }
const [error, result] = response; const [error, result] = response;
if (error) { if (error) {
reject(error); reject(error);
} else { } else {
resolve(result); resolve(result);
} }
});
}); });
}); }
showProgress() {
const message = this.isBurning() ? Msg.Lang['shell.burning'] : Msg.Lang['shell.uploading'];
this.#layer_.title(`${message}...`);
this.#layer_.show();
}
hideProgress() {
this.#layer_.hide();
}
isUploading() {
return this.#running_ && this.#upload_;
}
isBurning() {
return this.#running_ && !this.#upload_;
}
} }
showProgress() { WebSocket.BU = WebSocketBU;
const message = this.isBurning() ? Msg.Lang['shell.burning'] : Msg.Lang['shell.uploading'];
this.#layer_.title(`${message}...`);
this.#layer_.show();
}
hideProgress() {
this.#layer_.hide();
}
isUploading() {
return this.#running_ && this.#upload_;
}
isBurning() {
return this.#running_ && !this.#upload_;
}
}
WebSocket.BU = WebSocketBU;
}); });

View File

@@ -17,7 +17,7 @@ MString.tpl = (str, obj) => {
return str; return str;
} }
for (let key in obj) { for (let key in obj) {
let re = new RegExp(`{*${key}*}`, 'gim'); let re = new RegExp(`{${key}}`, 'gm');
str = str.replace(re, obj[key]); str = str.replace(re, obj[key]);
} }
return str; return str;

View File

@@ -14,7 +14,7 @@ export default class ShellMicroPython extends Shell {
indexPath: path.resolve(CLIENT_PATH, config.boardDirPath), indexPath: path.resolve(CLIENT_PATH, config.boardDirPath),
esptool: `"${PYTHON.path.cli}" "${MICROPYTHON.path.esptool}"`, esptool: `"${PYTHON.path.cli}" "${MICROPYTHON.path.esptool}"`,
com: config.port, com: config.port,
baudrate: config.baudRates baudrate: config.baudrate || "460800"
}; };
// 兼容性处理:移除模板中可能存在的冗余引号 // 兼容性处理:移除模板中可能存在的冗余引号
let cmdTemplate = config.command || ""; let cmdTemplate = config.command || "";
@@ -29,7 +29,7 @@ export default class ShellMicroPython extends Shell {
indexPath: path.resolve(CLIENT_PATH, config.boardDirPath), indexPath: path.resolve(CLIENT_PATH, config.boardDirPath),
ampy: `"${PYTHON.path.cli}" "${MICROPYTHON.path.ampy}"`, ampy: `"${PYTHON.path.cli}" "${MICROPYTHON.path.ampy}"`,
com: config.port, com: config.port,
reset: config.reset || "" reset: config.reset || "[]"
}; };
// 兼容性处理:移除模板中可能存在的冗余引号 // 兼容性处理:移除模板中可能存在的冗余引号
let cmdTemplate = config.command || ""; let cmdTemplate = config.command || "";