From 7126014619d4de70de5e208bab68eb6dcdf7c8d0 Mon Sep 17 00:00:00 2001 From: jake87 Date: Sat, 20 Nov 2021 16:23:21 +1100 Subject: [PATCH] Added editing for instances Instances require a number of modifications leading to it not being able to be converted into a function. The main one is that there is a list of config elements that cannot be set via the edit command and either have to be set via different means or cannot be set. To stop these fields constantly sending error messages, we disregard adding them to the file in the first place. --- packages/ctl/ctl.js | 121 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 3 deletions(-) diff --git a/packages/ctl/ctl.js b/packages/ctl/ctl.js index fd124e94..e99d7973 100755 --- a/packages/ctl/ctl.js +++ b/packages/ctl/ctl.js @@ -184,7 +184,10 @@ masterConfigCommands.add(new libCommand.Command({ }); } catch (err) { // eslint-disable-next-line - print(`Attempt to set ${element} to ${final[element] || String(null)} failed; set back to previous value.`); + print(`Attempt to set ${finalIndex} to ${part || String(null)} failed; set back to previous value.`); + print(err); + // 1. If the string is empty, it's better to just print "" instead of nothing + // 2. In my opinion this line is perfectly readable, and no need to rewright it } } } @@ -195,8 +198,6 @@ masterConfigCommands.add(new libCommand.Command({ if (err) { print("err: temporary file", tmpFile, "could not be deleted."); print("This is not fatal, but they may build up over time if the issue persists."); - print("Trace:"); - print(err); } }); }, @@ -398,6 +399,120 @@ instanceConfigCommands.add(new libCommand.Command({ await libLink.messages.setInstanceConfigProp.send(control, request); }, })); + +instanceConfigCommands.add(new libCommand.Command({ + definition: ["edit [editor]", "Edit instance configuration", (yargs) => { + yargs.positional("instance", { describe: "Instance to set config on", type: "string" }); + yargs.positional("editor", { describe: "Editor to use", + type: "string", + default: "" }); + }], + handler: async function(args, control) { + let instanceId = await libCommand.resolveInstance(control, args.instance); + let response = await libLink.messages.getInstanceConfig.send(control, { instance_id: instanceId }); + let tmpFile = await libFileOps.getTempFile("ctl-", "-tmp", os.tmpdir()); + let editor = 0; + if (args.editor) { + editor = args.editor; + /* eslint-disable */ + } else if (process.env.EDITOR) { + editor = process.env.EDITOR; + } else if (process.env.VISUAL) { + editor = process.env.VISUAL + /* eslint-enable */ + // needed for the process.env statements to not be flagged by eslint + // prio for editors is CLI arg > env.EDITOR > env.VISUAL + } else { + throw new libErrors.CommandError("No editor avalible. Checked CLI input, EDITOR and VISUAL env vars"); + } + + let allConfigElements = ""; + let disallowedList = {"instance.id": 0, "instance.assigned_slave": 0, "factorio.settings": 0}; + for (let group of response.serialized_config.groups) { + for (let [name, value] of Object.entries(group.fields)) { + // put quotes around value if it's a string + if (`${group.name}.${name}` in disallowedList) { + continue; + } + let desc = ""; + try { + desc += libConfig.MasterConfig.groups.get(group.name)._definitions.get(name).description; + } catch (err) { + desc += "No description found"; + } + allConfigElements += `# ${desc}\n`; + // split onto two lines for readability and es-lint + if (String(value) === "null") { + value = ""; + } + allConfigElements += `${group.name}.${name} = ${value}\n\n`; + } + } + // use same method as master config list to grab values, with a small modification to add quotes around strings + // this could be shorter but the lines would have to be very long, so imo this is better for readability + await fs.writeFile(tmpFile, allConfigElements, (err) => { + if (err) { + throw err; + } + }); + let editorSpawn = child_process.spawn(editor, [tmpFile], { + stdio: "inherit", + detached: false, + }); + editorSpawn.on("data", (data) => { + process.stdout.pipe(data); + }); + let emmiter = new events.EventEmitter(); + editorSpawn.on("exit", async (exit) => { + const data = await fs.readFile(tmpFile, "utf8"); + let splitData = data.split(/\r?\n/); + // split on newlines + let filtered = splitData.filter((value) => !(value[0] === "#")).filter((a) => a); + // the last filter removes empty elements left by the first. Not done on one line due to readability. + for (let index in filtered) { + if (index in filtered) { + filtered[index] = filtered[index].split("="); + let finalIndex = filtered[index][0].trim(); + // split on the = we added earlier + let part = ""; + try { + part = filtered[index][1].trim(); + // it's a string if we can read it + } catch (err) { + // if we can't read it, it's a empty field and therefor null + part = ""; + } + try { + await libLink.messages.setInstanceConfigField.send(control, { + instance_id: instanceId, + field: finalIndex, + value: part, + }); + } catch (err) { + // eslint-disable-next-line + print(`\n\n\nAttempt to set ${finalIndex} to ${JSON.stringify(part) || String(null)} failed; set back to previous value.`); + // 1. If the string is empty, it's better to just print "" instead of nothing + // 2. In my opinion this line is perfectly readable, and no need to rewright it + print("This message shouldn't normally appear; if the below message does not indicate it"); + print("was a user mistake, please report it to the clustorio devs."); + // added this because it could be a missed entry in disallowedList + // i've added all the vanilla clustorio configs, but there may be + // some modded ones that need to be added. + print(err); + } + } + } + emmiter.emit("dot_on_done"); + }); + await events.once(emmiter, "dot_on_done"); + await fs.unlink(tmpFile, (err) => { + if (err) { + print("err: temporary file", tmpFile, "could not be deleted."); + print("This is not fatal, but they may build up over time if the issue persists."); + } + }); + }, +})); instanceCommands.add(instanceConfigCommands); instanceCommands.add(new libCommand.Command({