diff --git a/coordinator/configure.lua b/coordinator/configure.lua index 88267cc..c0d3acd 100644 --- a/coordinator/configure.lua +++ b/coordinator/configure.lua @@ -80,6 +80,7 @@ local tool_ctl = { show_sv_cfg = nil, ---@type function start_fail = false, + fail_message = "", has_config = false, viewing_config = false, importing_legacy = false, @@ -316,7 +317,8 @@ local function config_view(display) TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the Coordinator configurator! Please select one of the following options."} if tool_ctl.start_fail == 2 then - TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: There is a problem with your monitor configuration. You may have lost a monitor or their sizes may be incorrect. Please reconfigure monitors or correct their sizes.",fg_bg=cpair(colors.red,colors.lightGray)} + local msg = util.c("Notice: There is a problem with your monitor configuration. ", tool_ctl.fail_message, " Please reconfigure monitors or correct their sizes.") + TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text=msg,fg_bg=cpair(colors.red,colors.lightGray)} y_start = y_start + 5 elseif tool_ctl.start_fail > 0 then TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device has no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)} @@ -1218,10 +1220,13 @@ local function reset_term() term.setCursorPos(1, 1) end --- run the coordinator configurator ----@param start_fail? integer indicate if this is being called by the coordinator startup app due to an invalid configuration -function configurator.configure(start_fail) +-- run the coordinator configurator
+-- start_fail of 0 is OK (not expected, default if not provided), 1 is bad config, 2 is bad monitor config +---@param start_fail? 0|1|2 indicate if this is being called by the startup app due to an invalid configuration +---@param message? any string message to display on a start_fail of 2 +function configurator.configure(start_fail, message) tool_ctl.start_fail = start_fail or 0 + tool_ctl.fail_message = util.trinary(type(message) == "string", message, "") load_settings(settings_cfg, true) tool_ctl.has_config = load_settings(ini_cfg) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 78c5f97..402827c 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -9,11 +9,6 @@ local process = require("coordinator.process") local apisessions = require("coordinator.session.apisessions") -local dialog = require("coordinator.ui.dialog") - -local print = util.print -local println = util.println - local PROTOCOL = comms.PROTOCOL local DEVICE_TYPE = comms.DEVICE_TYPE local ESTABLISH_ACK = comms.ESTABLISH_ACK @@ -26,32 +21,76 @@ local LINK_TIMEOUT = 60.0 local coordinator = {} --- request the user to select a monitor ----@nodiscard ----@param names table available monitors ----@return boolean|string|nil -local function ask_monitor(names) - println("available monitors:") - for i = 1, #names do - print(" " .. names[i]) - end - println("") - println("select a monitor or type c to cancel") +---@type crd_config +local config = {} - local iface = dialog.ask_options(names, "c") +coordinator.config = config - if iface ~= false and iface ~= nil then - util.filter_table(names, function (x) return x ~= iface end) +-- load the coordinator configuration
+-- status of 0 is OK, 1 is bad config, 2 is bad monitor config +---@return 0|1|2 status, nil|monitors_struct|string monitors +function coordinator.load_config() + if not settings.load("/coordinator.settings") then return 1 end + + config.UnitCount = settings.get("UnitCount") + config.SpeakerVolume = settings.get("SpeakerVolume") + config.Time24Hour = settings.get("Time24Hour") + + config.DisableFlowView = settings.get("DisableFlowView") + config.MainDisplay = settings.get("MainDisplay") + config.FlowDisplay = settings.get("FlowDisplay") + config.UnitDisplays = settings.get("UnitDisplays") + + config.SVR_Channel = settings.get("SVR_Channel") + config.CRD_Channel = settings.get("CRD_Channel") + config.PKT_Channel = settings.get("PKT_Channel") + config.SVR_Timeout = settings.get("SVR_Timeout") + config.API_Timeout = settings.get("API_Timeout") + config.TrustedRange = settings.get("TrustedRange") + config.AuthKey = settings.get("AuthKey") + + config.LogMode = settings.get("LogMode") + config.LogPath = settings.get("LogPath") + config.LogDebug = settings.get("LogDebug") + + local cfv = util.new_validator() + + cfv.assert_type_int(config.UnitCount) + cfv.assert_range(config.UnitCount, 1, 4) + cfv.assert_type_bool(config.Time24Hour) + + cfv.assert_type_bool(config.DisableFlowView) + cfv.assert_type_table(config.UnitDisplays) + + cfv.assert_type_num(config.SpeakerVolume) + cfv.assert_min(config.SpeakerVolume, 0.0) + cfv.assert_max(config.SpeakerVolume, 3.0) + + cfv.assert_channel(config.SVR_Channel) + cfv.assert_channel(config.CRD_Channel) + cfv.assert_channel(config.PKT_Channel) + + cfv.assert_type_num(config.SVR_Timeout) + cfv.assert_min(config.SVR_Timeout, 2) + cfv.assert_type_num(config.API_Timeout) + cfv.assert_min(config.API_Timeout, 2) + + cfv.assert_type_num(config.TrustedRange) + cfv.assert_min(config.TrustedRange, 0) + cfv.assert_type_str(config.AuthKey) + + if type(config.AuthKey) == "string" then + local len = string.len(config.AuthKey) + cfv.assert_eq(len == 0 or len >= 8, true) end - return iface -end + cfv.assert_type_int(config.LogMode) + cfv.assert_range(config.LogMode, 0, 1) + cfv.assert_type_str(config.LogPath) + cfv.assert_type_bool(config.LogDebug) + + -- Monitor Setup --- configure monitor layout ----@param num_units integer number of units expected ----@param disable_flow_view boolean disable flow view (legacy) ----@return boolean success, monitors_struct? monitors -function coordinator.configure_monitors(num_units, disable_flow_view) ---@class monitors_struct local monitors = { primary = nil, ---@type table|nil @@ -62,146 +101,70 @@ function coordinator.configure_monitors(num_units, disable_flow_view) unit_name_map = {} } - local monitors_avail = ppm.get_monitor_list() - local names = {} - local available = {} + local mon_cfv = util.new_validator() -- get all interface names - for iface, _ in pairs(monitors_avail) do - table.insert(names, iface) - table.insert(available, iface) - end + local names = {} + for iface, _ in pairs(ppm.get_monitor_list()) do table.insert(names, iface) end - -- we need a certain number of monitors (1 per unit + 1 primary display + 1 flow display) - local num_displays_needed = num_units + util.trinary(disable_flow_view, 1, 2) - if #names < num_displays_needed then - local message = "not enough monitors connected (need " .. num_displays_needed .. ")" - println(message) - log.warning(message) - return false - end + local function setup_monitors() + mon_cfv.assert_type_str(config.MainDisplay) + if not config.DisableFlowView then mon_cfv.assert_type_str(config.FlowDisplay) end + mon_cfv.assert_eq(#config.UnitDisplays, config.UnitCount) - -- attempt to load settings - if not settings.load("/coord.settings") then - log.warning("configure_monitors(): failed to load coordinator settings file (may not exist yet)") - else - local _primary = settings.get("PRIMARY_DISPLAY") - local _flow = settings.get("FLOW_DISPLAY") - local _unitd = settings.get("UNIT_DISPLAYS") + if mon_cfv.valid() then + mon_cfv.assert(util.table_contains(names, config.MainDisplay)) - -- filter out already assigned monitors - util.filter_table(available, function (x) return x ~= _primary end) - util.filter_table(available, function (x) return x ~= _flow end) - if type(_unitd) == "table" then - util.filter_table(available, function (x) return not util.table_contains(_unitd, x) end) - end - end + if not mon_cfv.valid() then return 2, "Main monitor is not connected." end - --------------------- - -- PRIMARY DISPLAY -- - --------------------- + monitors.primary = ppm.get_periph(config.MainDisplay) + monitors.primary_name = config.MainDisplay - local iface_primary_display = settings.get("PRIMARY_DISPLAY") ---@type boolean|string|nil + local w, h = ppm.monitor_block_size(monitors.primary.getSize()) + mon_cfv.assert(w == 8) - if not util.table_contains(names, iface_primary_display) then - println("primary display is not connected") - local response = dialog.ask_y_n("would you like to change it", true) - if response == false then return false end - iface_primary_display = nil - end + if not mon_cfv.valid() then return 2, "Main monitor width is incorrect." end - while iface_primary_display == nil and #available > 0 do - iface_primary_display = ask_monitor(available) - end + if not config.DisableFlowView then + mon_cfv.assert(util.table_contains(names, config.FlowDisplay)) - if type(iface_primary_display) ~= "string" then return false end + if not mon_cfv.valid() then return 2, "Flow monitor is not connected." end - settings.set("PRIMARY_DISPLAY", iface_primary_display) - util.filter_table(available, function (x) return x ~= iface_primary_display end) + monitors.flow = ppm.get_periph(config.FlowDisplay) + monitors.flow_name = config.FlowDisplay - monitors.primary = ppm.get_periph(iface_primary_display) - monitors.primary_name = iface_primary_display + w, h = ppm.monitor_block_size(monitors.flow.getSize()) + mon_cfv.assert(w == 8) - -------------------------- - -- FLOW MONITOR DISPLAY -- - -------------------------- - - if not disable_flow_view then - local iface_flow_display = settings.get("FLOW_DISPLAY") ---@type boolean|string|nil - - if not util.table_contains(names, iface_flow_display) then - println("flow monitor display is not connected") - local response = dialog.ask_y_n("would you like to change it", true) - if response == false then return false end - iface_flow_display = nil - end - - while iface_flow_display == nil and #available > 0 do - iface_flow_display = ask_monitor(available) - end - - if type(iface_flow_display) ~= "string" then return false end - - settings.set("FLOW_DISPLAY", iface_flow_display) - util.filter_table(available, function (x) return x ~= iface_flow_display end) - - monitors.flow = ppm.get_periph(iface_flow_display) - monitors.flow_name = iface_flow_display - end - - ------------------- - -- UNIT DISPLAYS -- - ------------------- - - local unit_displays = settings.get("UNIT_DISPLAYS") - - if unit_displays == nil then - unit_displays = {} - for i = 1, num_units do - local display = nil - - while display == nil and #available > 0 do - println("please select monitor for unit #" .. i) - display = ask_monitor(available) + if not mon_cfv.valid() then return 2, "Flow monitor width is incorrect." end end - if display == false then return false end + for i = 1, config.UnitCount do + local display = config.UnitDisplays[i] - unit_displays[i] = display - end - else - -- make sure all displays are connected - for i = 1, num_units do - local display = unit_displays[i] + mon_cfv.assert_type_str(display) + mon_cfv.assert(util.table_contains(names, display)) - if not util.table_contains(names, display) then - println("unit #" .. i .. " display is not connected") - local response = dialog.ask_y_n("would you like to change it", true) - if response == false then return false end - display = nil + if not mon_cfv.valid() then return 2, "Unit " .. i .. " monitor is not connected." end + + monitors.unit_displays[i] = ppm.get_periph(display) + monitors.unit_name_map[i] = display + + w, h = ppm.monitor_block_size(monitors.unit_displays[i].getSize()) + mon_cfv.assert(w == 4 and h == 4) + + if not mon_cfv.valid() then return 2, "Unit " .. i .. " monitor size is incorrect." end end - - while display == nil and #available > 0 do - display = ask_monitor(available) - end - - if display == false then return false end - - unit_displays[i] = display - end + else return 2, "Monitor configuration invalid." end end - settings.set("UNIT_DISPLAYS", unit_displays) - if not settings.save("/coord.settings") then - log.warning("configure_monitors(): failed to save coordinator settings file") - end + if cfv.valid() then + local ok, result, message = pcall(setup_monitors) + assert(ok, util.c("fatal error while trying to verify monitors: ", result)) + if result == 2 then return 2, message end + else return 1 end - for i = 1, #unit_displays do - monitors.unit_displays[i] = ppm.get_periph(unit_displays[i]) - monitors.unit_name_map[i] = unit_displays[i] - end - - return true, monitors + return 0, monitors end -- dmesg print wrapper @@ -246,13 +209,8 @@ end ---@nodiscard ---@param version string coordinator version ---@param nic nic network interface device ----@param num_units integer number of configured units for number of monitors, checked against SV ----@param crd_channel integer port of configured supervisor ----@param svr_channel integer listening port for supervisor replys ----@param pkt_channel integer listening port for pocket API ----@param range integer trusted device connection range ---@param sv_watchdog watchdog -function coordinator.comms(version, nic, num_units, crd_channel, svr_channel, pkt_channel, range, sv_watchdog) +function coordinator.comms(version, nic, sv_watchdog) local self = { sv_linked = false, sv_addr = comms.BROADCAST, @@ -267,11 +225,11 @@ function coordinator.comms(version, nic, num_units, crd_channel, svr_channel, pk est_task_done = nil } - comms.set_trusted_range(range) + comms.set_trusted_range(config.TrustedRange) -- configure network channels nic.closeAll() - nic.open(crd_channel) + nic.open(config.CRD_Channel) -- link nic to apisessions apisessions.init(nic) @@ -296,7 +254,7 @@ function coordinator.comms(version, nic, num_units, crd_channel, svr_channel, pk pkt.make(msg_type, msg) s_pkt.make(self.sv_addr, self.sv_seq_num, protocol, pkt.raw_sendable()) - nic.transmit(svr_channel, crd_channel, s_pkt) + nic.transmit(config.SVR_Channel, config.CRD_Channel, s_pkt) self.sv_seq_num = self.sv_seq_num + 1 end @@ -310,7 +268,7 @@ function coordinator.comms(version, nic, num_units, crd_channel, svr_channel, pk m_pkt.make(MGMT_TYPE.ESTABLISH, { ack }) s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - nic.transmit(pkt_channel, crd_channel, s_pkt) + nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt) self.last_api_est_acks[packet.src_addr()] = ack end @@ -343,7 +301,7 @@ function coordinator.comms(version, nic, num_units, crd_channel, svr_channel, pk self.est_last = self.est_start self.est_tick_waiting, self.est_task_done = - coordinator.log_comms_connecting("attempting to connect to configured supervisor on channel " .. svr_channel) + coordinator.log_comms_connecting("attempting to connect to configured supervisor on channel " .. config.SVR_Channel) _send_establish() else @@ -356,7 +314,7 @@ function coordinator.comms(version, nic, num_units, crd_channel, svr_channel, pk if abort then coordinator.log_comms("supervisor connection attempt cancelled by user") elseif self.sv_config_err then - coordinator.log_comms("supervisor cooling configuration invalid, check supervisor config file") + coordinator.log_comms("supervisor unit count does not match coordinator unit count, check configs") elseif not self.sv_linked then if self.last_est_ack == ESTABLISH_ACK.DENY then coordinator.log_comms("supervisor connection attempt denied") @@ -371,7 +329,7 @@ function coordinator.comms(version, nic, num_units, crd_channel, svr_channel, pk ok = false elseif self.sv_config_err then - coordinator.log_comms("supervisor cooling configuration invalid, check supervisor config file") + coordinator.log_comms("supervisor unit count does not match coordinator unit count, check configs") ok = false elseif (util.time_s() - self.est_last) > 1.0 then _send_establish() @@ -464,9 +422,9 @@ function coordinator.comms(version, nic, num_units, crd_channel, svr_channel, pk local src_addr = packet.scada_frame.src_addr() local protocol = packet.scada_frame.protocol() - if l_chan ~= crd_channel then + if l_chan ~= config.CRD_Channel then log.debug("received packet on unconfigured channel " .. l_chan, true) - elseif r_chan == pkt_channel then + elseif r_chan == config.PKT_Channel then if not self.sv_linked then log.debug("discarding pocket API packet before linked to supervisor") elseif protocol == PROTOCOL.SCADA_CRDN then @@ -526,7 +484,7 @@ function coordinator.comms(version, nic, num_units, crd_channel, svr_channel, pk else log.debug("illegal packet type " .. protocol .. " on pocket channel", true) end - elseif r_chan == svr_channel then + elseif r_chan == config.SVR_Channel then -- check sequence number if self.sv_r_seq_num == nil then self.sv_r_seq_num = packet.scada_frame.seq_num() @@ -699,22 +657,22 @@ function coordinator.comms(version, nic, num_units, crd_channel, svr_channel, pk -- connection with supervisor established if packet.length == 2 then local est_ack = packet.data[1] - local config = packet.data[2] + local sv_config = packet.data[2] if est_ack == ESTABLISH_ACK.ALLOW then -- reset to disconnected before validating iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED) - if type(config) == "table" and #config == 2 then + if type(sv_config) == "table" and #sv_config == 2 then -- get configuration ---@class facility_conf local conf = { - num_units = config[1], ---@type integer - cooling = config[2] ---@type sv_cooling_conf + num_units = sv_config[1], ---@type integer + cooling = sv_config[2] ---@type sv_cooling_conf } - if conf.num_units == num_units then + if conf.num_units == config.UnitCount then -- init io controller iocontrol.init(conf, public) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 5aadbb2..f9c793d 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -4,6 +4,7 @@ local log = require("scada-common.log") local util = require("scada-common.util") +local ppm = require("scada-common.ppm") local iocontrol = require("coordinator.iocontrol") @@ -93,39 +94,6 @@ function renderer.init_displays() end end --- check main display width ----@nodiscard ----@return boolean width_okay -function renderer.validate_main_display_width() - local w, _ = engine.monitors.primary.getSize() - return w == 164 -end - --- check flow display width ----@nodiscard ----@return boolean width_okay -function renderer.validate_flow_display_width() - local w, _ = engine.monitors.flow.getSize() - return w == 164 -end - --- check display sizes ----@nodiscard ----@return boolean valid all unit display dimensions OK -function renderer.validate_unit_display_sizes() - local valid = true - - for id, monitor in ipairs(engine.monitors.unit_displays) do - local w, h = monitor.getSize() - if w ~= 79 or h ~= 52 then - log.warning(util.c("RENDERER: unit ", id, " display resolution not 79 wide by 52 tall: ", w, ", ", h)) - valid = false - end - end - - return valid -end - -- initialize the dmesg output window function renderer.init_dmesg() local disp_x, disp_y = engine.monitors.primary.getSize() diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 2f56527..4301bac 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -14,7 +14,7 @@ local util = require("scada-common.util") local core = require("graphics.core") -local config = require("coordinator.config") +local configure = require("coordinator.configure") local coordinator = require("coordinator.coordinator") local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") @@ -34,32 +34,34 @@ local log_comms = coordinator.log_comms local log_crypto = coordinator.log_crypto ---------------------------------------- --- config validation +-- get configuration ---------------------------------------- -local cfv = util.new_validator() +-- mount connected devices (required for monitor setup) +ppm.mount_all() -cfv.assert_channel(config.SVR_CHANNEL) -cfv.assert_channel(config.CRD_CHANNEL) -cfv.assert_channel(config.PKT_CHANNEL) -cfv.assert_type_int(config.TRUSTED_RANGE) -cfv.assert_type_num(config.SV_TIMEOUT) -cfv.assert_min(config.SV_TIMEOUT, 2) -cfv.assert_type_num(config.API_TIMEOUT) -cfv.assert_min(config.API_TIMEOUT, 2) -cfv.assert_type_int(config.NUM_UNITS) -cfv.assert_type_num(config.SOUNDER_VOLUME) -cfv.assert_type_bool(config.TIME_24_HOUR) -cfv.assert_type_str(config.LOG_PATH) -cfv.assert_type_int(config.LOG_MODE) +local loaded, monitors = coordinator.load_config() +if loaded ~= 0 then + -- try to reconfigure (user action) + local success, error = configure.configure(loaded, monitors) + if success then + loaded, monitors = coordinator.load_config() + assert(loaded == 0, util.trinary(loaded == 1, "failed to load valid configuration", "monitor configuration invalid")) + else + assert(success, "coordinator configuration error: " .. error) + end +end -assert(cfv.valid(), "bad config file: missing/invalid fields") +-- passed checks, good now +---@cast monitors monitors_struct + +local config = coordinator.config ---------------------------------------- -- log init ---------------------------------------- -log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true) +log.init(config.LogPath, config.LogMode, config.LogDebug) log.info("========================================") log.info("BOOTING coordinator.startup " .. COORDINATOR_VERSION) @@ -77,39 +79,13 @@ local function main() -- system startup ---------------------------------------- - -- mount connected devices - ppm.mount_all() - -- report versions/init fp PSIL iocontrol.init_fp(COORDINATOR_VERSION, comms.version) - -- setup monitors - local configured, monitors = coordinator.configure_monitors(config.NUM_UNITS, config.DISABLE_FLOW_VIEW == true) - if not configured or monitors == nil then - println("startup> monitor setup failed") - log.fatal("monitor configuration failed") - return - end - -- init renderer - renderer.legacy_disable_flow_view(config.DISABLE_FLOW_VIEW == true) + renderer.legacy_disable_flow_view(config.DisableFlowView) renderer.set_displays(monitors) renderer.init_displays() - - if not renderer.validate_main_display_width() then - println("startup> main display must be 8 blocks wide") - log.fatal("main display not wide enough") - return - elseif (config.DISABLE_FLOW_VIEW ~= true) and not renderer.validate_flow_display_width() then - println("startup> flow display must be 8 blocks wide") - log.fatal("flow display not wide enough") - return - elseif not renderer.validate_unit_display_sizes() then - println("startup> one or more unit display dimensions incorrect; they must be 4x4 blocks") - log.fatal("unit display dimensions incorrect") - return - end - renderer.init_dmesg() -- lets get started! @@ -132,7 +108,7 @@ local function main() else local sounder_start = util.time_ms() log_boot("annunciator alarm speaker connected") - sounder.init(speaker, config.SOUNDER_VOLUME) + sounder.init(speaker, config.SpeakerVolume) log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms") log_sys("annunciator alarm configured") iocontrol.fp_has_speaker(true) @@ -143,8 +119,8 @@ local function main() ---------------------------------------- -- message authentication init - if type(config.AUTH_KEY) == "string" then - local init_time = network.init_mac(config.AUTH_KEY) + if type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0 then + local init_time = network.init_mac(config.AuthKey) log_crypto("HMAC init took " .. init_time .. "ms") end @@ -161,14 +137,13 @@ local function main() end -- create connection watchdog - local conn_watchdog = util.new_watchdog(config.SV_TIMEOUT) + local conn_watchdog = util.new_watchdog(config.SVR_Timeout) conn_watchdog.cancel() log.debug("startup> conn watchdog created") -- create network interface then setup comms local nic = network.nic(modem) - local coord_comms = coordinator.comms(COORDINATOR_VERSION, nic, config.NUM_UNITS, config.CRD_CHANNEL, - config.SVR_CHANNEL, config.PKT_CHANNEL, config.TRUSTED_RANGE, conn_watchdog) + local coord_comms = coordinator.comms(COORDINATOR_VERSION, nic, conn_watchdog) log.debug("startup> comms init") log_comms("comms initialized") @@ -214,7 +189,7 @@ local function main() local link_failed = false local ui_ok = true - local date_format = util.trinary(config.TIME_24_HOUR, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y") + local date_format = util.trinary(config.Time24Hour, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y") -- start clock loop_clock.start() diff --git a/coordinator/ui/dialog.lua b/coordinator/ui/dialog.lua deleted file mode 100644 index 676ae2b..0000000 --- a/coordinator/ui/dialog.lua +++ /dev/null @@ -1,52 +0,0 @@ -local completion = require("cc.completion") - -local util = require("scada-common.util") - -local print = util.print - -local dialog = {} - --- ask the user yes or no ----@nodiscard ----@param question string ----@param default boolean ----@return boolean|nil -function dialog.ask_y_n(question, default) - print(question) - - if default == true then - print(" (Y/n)? ") - else - print(" (y/N)? ") - end - - local response = read(nil, nil) - - if response == "" then - return default - elseif response == "Y" or response == "y" then - return true - elseif response == "N" or response == "n" then - return false - else - return nil - end -end - --- ask the user for an input within a set of options ----@nodiscard ----@param options table ----@param cancel string ----@return boolean|string|nil -function dialog.ask_options(options, cancel) - print("> ") - local response = read(nil, nil, function(text) return completion.choice(text, options) end) - - if response == cancel then return false end - - if util.table_contains(options, response) then - return response - else return nil end -end - -return dialog