| --[[ |
| /* |
| * 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 .. " <cr>", "[ ]+") |
| 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 .. " <cr>", "[ ]+") |
| 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_debug-native/vpp-api/lib64/libpneum.so" |
| |
| vpp:init({ pneum_path = pneum_path }) |
| |
| vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api") |
| vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api") |
| |
| 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 ipairs(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() |
| |
| |