--[[ /* * Copyright (c) 2016 Cisco and/or its affiliates. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ ]] -- Experimental prototype CLI using API to VPP, with tab completion -- -- Written by Andrew Yourtchenko (ayourtch@cisco.com) 2010,2016 -- vpp = require "vpp-lapi" local dotdotdot = "..." -- First the "readline" routine readln = { split = function(str, pat) local t = {} -- NOTE: use {n = 0} in Lua-5.0 local fpat = "(.-)" .. pat local last_end = 1 if str then local s, e, cap = str:find(fpat, 1) while s do if s ~= 1 or cap ~= "" then table.insert(t,cap) end last_end = e+1 s, e, cap = str:find(fpat, last_end) end if last_end <= #str then cap = str:sub(last_end) table.insert(t, cap) end end return t end, reader = function() local rl = {} rl.init = function() os.execute("stty -icanon min 1 -echo") rl.rawmode = true end rl.done = function() os.execute("stty icanon echo") rl.rawmode = false end rl.prompt = ">" rl.history = { "" } rl.history_index = 1 rl.history_length = 1 rl.hide_cmd = function() local bs = string.char(8) .. " " .. string.char(8) for i = 1, #rl.command do io.stdout:write(bs) end end rl.show_cmd = function() if rl.command then io.stdout:write(rl.command) end end rl.store_history = function(cmd) if cmd == "" then return end rl.history[rl.history_length] = cmd rl.history_length = rl.history_length + 1 rl.history_index = rl.history_length rl.history[rl.history_length] = "" end rl.readln = function() local done = false local need_prompt = true rl.command = "" if not rl.rawmode then rl.init() end while not done do if need_prompt then io.stdout:write(rl.prompt) io.stdout:write(rl.command) need_prompt = false end local ch = io.stdin:read(1) if ch:byte(1) == 27 then -- CONTROL local ch2 = io.stdin:read(1) -- arrows if ch2:byte(1) == 91 then local ch3 = io.stdin:read(1) local b = ch3:byte(1) if b == 65 then ch = "UP" elseif b == 66 then ch = "DOWN" elseif b == 67 then ch = "RIGHT" elseif b == 68 then ch = "LEFT" end -- print("Byte: " .. ch3:byte(1)) -- if ch3:byte(1) end end if ch == "?" then io.stdout:write(ch) io.stdout:write("\n") if rl.help then rl.help(rl) end need_prompt = true elseif ch == "\t" then if rl.tab_complete then rl.tab_complete(rl) end io.stdout:write("\n") need_prompt = true elseif ch == "\n" then io.stdout:write(ch) done = true elseif ch == "\004" then io.stdout:write("\n") rl.command = nil done = true elseif ch == string.char(127) then if rl.command ~= "" then io.stdout:write(string.char(8) .. " " .. string.char(8)) rl.command = string.sub(rl.command, 1, -2) end elseif #ch > 1 then -- control char if ch == "UP" then rl.hide_cmd() if rl.history_index == #rl.history then rl.history[rl.history_index] = rl.command end if rl.history_index > 1 then rl.history_index = rl.history_index - 1 rl.command = rl.history[rl.history_index] end rl.show_cmd() elseif ch == "DOWN" then rl.hide_cmd() if rl.history_index < rl.history_length then rl.history_index = rl.history_index + 1 rl.command = rl.history[rl.history_index] end rl.show_cmd() end else io.stdout:write(ch) rl.command = rl.command .. ch end end if rl.command then rl.store_history(rl.command) end return rl.command end return rl end } --[[ r = reader() local done = false while not done do local cmd = r.readln() print("Command: " .. tostring(cmd)) if not cmd or cmd == "quit" then done = true end end r.done() ]] --------- MDS show tech parser local print_section = nil local list_sections = false local curr_section = "---" local curr_parser = nil -- by default operate in batch mode local batch_mode = true local db = {} local device = {} device.output = {} local seen_section = {} function start_collection(name) device = {} seen_section = {} end function print_error(errmsg) print("@#$:" .. errmsg) end function keys(tbl) local t = {} for k, v in pairs(tbl) do table.insert(t, k) end return t end function tset (parent, ...) -- print ('set', ...) local len = select ('#', ...) local key, value = select (len-1, ...) local cutpoint, cutkey for i=1,len-2 do local key = select (i, ...) local child = parent[key] if value == nil then if child == nil then return elseif next (child, next (child)) then cutpoint = nil cutkey = nil elseif cutpoint == nil then cutpoint = parent cutkey = key end elseif child == nil then child = {} parent[key] = child end parent = child end if value == nil and cutpoint then cutpoint[cutkey] = nil else parent[key] = value return value end end function tget (parent, ...) local len = select ('#', ...) for i=1,len do parent = parent[select (i, ...)] if parent == nil then break end end return parent end local pager_lines = 23 local pager_printed = 0 local pager_skipping = false local pager_filter_pipe = nil function pager_reset() pager_printed = 0 pager_skipping = false if pager_filter_pipe then pager_filter_pipe:close() pager_filter_pipe = nil end end function print_more() io.stdout:write(" --More-- ") end function print_nomore() local bs = string.char(8) local bs10 = bs .. bs .. bs .. bs .. bs .. bs .. bs .. bs .. bs .. bs io.stdout:write(bs10 .. " " .. bs10) end function print_line(txt) if pager_filter_pipe then pager_filter_pipe:write(txt .. "\n") return end if pager_printed >= pager_lines then print_more() local ch = io.stdin:read(1) if ch == " " then pager_printed = 0 elseif ch == "\n" then pager_printed = pager_printed - 1 elseif ch == "q" then pager_printed = 0 pager_skipping = true end print_nomore() end if not pager_skipping then print(txt) pager_printed = pager_printed + 1 else -- skip printing end end function paged_write(text) local t = readln.split(text, "[\n]") if string.sub(text, -1) == "\n" then table.insert(t, "") end for i, v in ipairs(t) do if i < #t then print_line(v) else if pager_filter_pipe then pager_filter_pipe:write(v) else io.stdout:write(v) end end end end function get_choices(tbl, key) local res = {} for k, v in pairs(tbl) do if string.sub(k, 1, #key) == key then table.insert(res, k) elseif 0 < #key and dotdotdot == k then table.insert(res, k) end end return res end function get_exact_choice(choices, val) local exact_idx = nil local substr_idx = nil local substr_seen = false if #choices == 1 then if choices[1] == dotdotdot then return 1 elseif string.sub(choices[1], 1, #val) == val then return 1 else return nil end else for i, v in ipairs(choices) do if v == val then exact_idx = i substr_seen = true elseif choices[i] ~= dotdotdot and string.sub(choices[i], 1, #val) == val then if substr_seen then substr_idx = nil else substr_idx = i substr_seen = true end elseif choices[i] == dotdotdot then if substr_seen then substr_idx = nil else substr_idx = i substr_seen = true end end end end return exact_idx or substr_idx end function device_cli_help(rl) local key = readln.split(rl.command, "[ ]+") local tree = rl.tree local keylen = #key local fullcmd = "" local error = false local terse = true if ((#rl.command >= 1) and (string.sub(rl.command, -1) == " ")) or (#rl.command == 0) then table.insert(key, "") terse = false end for i, v in ipairs(key) do local choices = get_choices(tree, v) local idx = get_exact_choice(choices, v) if idx then local choice = choices[idx] tree = tree[choice] fullcmd = fullcmd .. choice .. " " else if i < #key then error = true end end if i == #key and not error then for j, w in ipairs(choices) do if terse then paged_write(w .. "\t") else paged_write(" " .. w .. "\n") end end paged_write("\n") if terse then paged_write(" \n") end end end pager_reset() end function device_cli_tab_complete(rl) local key = readln.split(rl.command, "[ ]+") local tree = rl.tree local keylen = #key local fullcmd = "" local error = false for i, v in ipairs(key) do local choices = get_choices(tree, v) local idx = get_exact_choice(choices, v) if idx and choices[idx] ~= dotdotdot then local choice = choices[idx] tree = tree[choice] -- print("level " .. i .. " '" .. choice .. "'") fullcmd = fullcmd .. choice .. " " else -- print("level " .. i .. " : " .. table.concat(choices, " ") .. " ") error = true end end if not error then rl.command = fullcmd else -- print("\n\nerror\n") end pager_reset() end function device_cli_exec(rl) local cmd_nopipe = rl.command local cmd_pipe = nil local pipe1, pipe2 = string.find(rl.command, "[|]") if pipe1 then cmd_nopipe = string.sub(rl.command, 1, pipe1-1) cmd_pipe = string.sub(rl.command, pipe2+1, -1) end local key = readln.split(cmd_nopipe .. " ", "[ ]+") local tree = rl.tree local keylen = #key local fullcmd = "" local error = false local func = nil if cmd_pipe then pager_filter_pipe = io.popen(cmd_pipe, "w") end rl.choices = {} for i, v in ipairs(key) do local choices = get_choices(tree, v) local idx = get_exact_choice(choices, v) if idx then local choice = choices[idx] if i == #key then func = tree[choice] else if choice == dotdotdot then -- keep the tree the same, update the choice value to match the input string choices[idx] = v choice = v else tree = tree[choice] end end -- print("level " .. i .. " '" .. choice .. "'") table.insert(rl.choices, choice) else -- print("level " .. i .. " : " .. table.concat(choices, " ") .. " ") error = true return nil end end return func end function populate_tree(commands) local tree = {} for k, v in pairs(commands) do local key = readln.split(k .. " ", "[ ]+") local xtree = tree for i, kk in ipairs(key) do if i == 1 and kk == "sh" then kk = "show" end if i == #key then if type(v) == "function" then xtree[kk] = v else xtree[kk] = function(rl) paged_write(table.concat(v, "\n") .. "\n") end end else if not xtree[kk] then xtree[kk] = {} end xtree = xtree[kk] end end end return tree end function trim (s) return (string.gsub(s, "^%s*(.-)%s*$", "%1")) end function init_vpp(vpp) local root_dir = "/home/ubuntu/vpp" local pneum_path = root_dir .. "/build-root/install-vpp_lite_debug-native/vpp-api/lib64/libpneum.so" vpp:init({ pneum_path = pneum_path }) vpp:init({ pneum_path = pneum_path }) vpp:json_api(root_dir .. "/build-root/install-vpp_lite_debug-native/vpp/vpp-api/vpe.api.json") vpp:connect("lua_cli") end function run_cli(vpp, cli) local reply = vpp:api_call("cli_inband", { cmd = cli }) if reply and #reply == 1 then local rep = reply[1] if 0 == rep.retval then return rep.reply else return "XXXXXLUACLI: API RETVAL ERROR : " .. tostring(rep.retval) end else return "XXXXXLUACLI ERROR, RAW REPLY: " .. vpp.dump(reply) end end function toprintablestring(s) if type(s) == "string" then return "\n"..vpp.hex_dump(s) else return tostring(s) end end function interactive_cli(r) while not done do pager_reset() local cmd = r.readln() if not cmd then done = true elseif cmd == "quit" or cmd == "exit" then done = true else local func = device_cli_exec(r) if func then func(r) else if trim(cmd) == "" then else for i = 1, #r.prompt do paged_write(" ") end paged_write("^\n% Invalid input detected at '^' marker.\n\n") end end end end end device = {} device.output = {} init_vpp(vpp) cmds_str = run_cli(vpp, "?") vpp_cmds = readln.split(cmds_str, "\n") vpp_clis = {} for linenum, line in ipairs(vpp_cmds) do local m,h = string.match(line, "^ (.-) (.*)$") if m and #m > 0 then table.insert(vpp_clis, m) device.output["vpp debug cli " .. m] = function(rl) -- print("ARBITRARY CLI" .. vpp.dump(rl.choices)) print("LUACLI command: " .. table.concat(rl.choices, " ")) local sub = {} -- for i=4, #rl.choices -1 do table.insert(sub, rl.choices[i]) end local cli = table.concat(sub, " ") print("Running CLI: " .. tostring(cli)) paged_write(run_cli(vpp, cli)) end device.output["vpp debug cli " .. m .. " " .. dotdotdot] = function(rl) print("ARGH") end local ret = run_cli(vpp, "help " .. m) device.output["help vpp debug cli " .. m] = { ret } end end for linenum, line in ipairs(vpp_clis) do -- print(line, ret) end for msgnum, msgname in pairs(vpp.msg_number_to_name) do local cli, numspaces = string.gsub(msgname, "_", " ") device.output["call " .. cli .. " " .. dotdotdot] = function(rl) print("ARGH") end device.output["call " .. cli] = function(rl) print("LUACLI command: " .. table.concat(rl.choices, " ")) print("Running API: " .. msgname) -- vpp.dump(rl.choices)) local out = {} local args = {} local ntaken = 0 local argname = "" for i=(1+1+numspaces+1), #rl.choices-1 do -- print(i, rl.choices[i]) if ntaken > 0 then ntaken = ntaken -1 else local fieldname = rl.choices[i] local field = vpp.msg_name_to_fields[msgname][fieldname] if field then local s = rl.choices[i+1] s=s:gsub("\\x(%x%x)",function (x) return string.char(tonumber(x,16)) end) args[fieldname] = s ntaken = 1 end end end -- print("ARGS: ", vpp.dump(args)) local ret = vpp:api_call(msgname, args) for i, reply in ipairs(ret) do table.insert(out, "=================== Entry #" .. tostring(i)) for k, v in pairs(reply) do table.insert(out, " " .. tostring(k) .. " : " .. toprintablestring(v)) end end -- paged_write(vpp.dump(ret) .. "\n\n") paged_write(table.concat(out, "\n").."\n\n") end device.output["call " .. cli .. " help"] = function(rl) local out = {} for k, v in pairs(vpp.msg_name_to_fields[msgname]) do table.insert(out, tostring(k) .. " : " .. v["ctype"] .. " ; " .. tostring(vpp.dump(v)) ) end -- paged_write(vpp.dump(vpp.msg_name_to_fields[msgname]) .. "\n\n") paged_write(table.concat(out, "\n").."\n\n") end -- vpp.msg_name_to_number = {} end local r = readln.reader() local done = false r.prompt = "VPP(luaCLI)#" r.help = device_cli_help r.tab_complete = device_cli_tab_complete print("===== CLI view, use ^D to end =====") r.tree = populate_tree(device.output) -- readln.pretty("xxxx", r.tree) for idx, an_arg in ipairs(arg) do local fname = an_arg if fname == "-i" then pager_lines = 23 interactive_cli(r) else pager_lines = 100000000 for line in io.lines(fname) do r.command = line local func = device_cli_exec(r) if func then func(r) end end end end if #arg == 0 then print("You should specify '-i' as an argument for the interactive session,") print("but with no other sources of commands, we start interactive session now anyway") interactive_cli(r) end vpp:disconnect() r.done()