3 * Copyright (c) 2016 Cisco and/or its affiliates.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at:
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 -- Experimental prototype CLI using API to VPP, with tab completion
20 -- Written by Andrew Yourtchenko (ayourtch@cisco.com) 2010,2016
23 vpp = require "vpp-lapi"
26 local dotdotdot = "..."
28 -- First the "readline" routine
31 split = function(str, pat)
32 local t = {} -- NOTE: use {n = 0} in Lua-5.0
33 local fpat = "(.-)" .. pat
36 local s, e, cap = str:find(fpat, 1)
38 if s ~= 1 or cap ~= "" then
42 s, e, cap = str:find(fpat, last_end)
44 if last_end <= #str then
45 cap = str:sub(last_end)
56 os.execute("stty -icanon min 1 -echo")
61 os.execute("stty icanon echo")
70 rl.hide_cmd = function()
71 local bs = string.char(8) .. " " .. string.char(8)
72 for i = 1, #rl.command do
77 rl.show_cmd = function()
79 io.stdout:write(rl.command)
83 rl.store_history = function(cmd)
87 rl.history[rl.history_length] = cmd
88 rl.history_length = rl.history_length + 1
89 rl.history_index = rl.history_length
90 rl.history[rl.history_length] = ""
93 rl.readln = function()
95 local need_prompt = true
98 if not rl.rawmode then
104 io.stdout:write(rl.prompt)
105 io.stdout:write(rl.command)
109 local ch = io.stdin:read(1)
110 if ch:byte(1) == 27 then
112 local ch2 = io.stdin:read(1)
114 if ch2:byte(1) == 91 then
115 local ch3 = io.stdin:read(1)
116 local b = ch3:byte(1)
126 -- print("Byte: " .. ch3:byte(1))
133 io.stdout:write("\n")
138 elseif ch == "\t" then
139 if rl.tab_complete then
142 io.stdout:write("\n")
144 elseif ch == "\n" then
147 elseif ch == "\004" then
148 io.stdout:write("\n")
151 elseif ch == string.char(127) then
152 if rl.command ~= "" then
153 io.stdout:write(string.char(8) .. " " .. string.char(8))
154 rl.command = string.sub(rl.command, 1, -2)
160 if rl.history_index == #rl.history then
161 rl.history[rl.history_index] = rl.command
163 if rl.history_index > 1 then
164 rl.history_index = rl.history_index - 1
165 rl.command = rl.history[rl.history_index]
168 elseif ch == "DOWN" then
170 if rl.history_index < rl.history_length then
171 rl.history_index = rl.history_index + 1
172 rl.command = rl.history[rl.history_index]
178 rl.command = rl.command .. ch
182 rl.store_history(rl.command)
198 local cmd = r.readln()
199 print("Command: " .. tostring(cmd))
200 if not cmd or cmd == "quit" then
209 --------- MDS show tech parser
211 local print_section = nil
212 local list_sections = false
214 local curr_section = "---"
215 local curr_parser = nil
217 -- by default operate in batch mode
218 local batch_mode = true
223 local seen_section = {}
225 function start_collection(name)
230 function print_error(errmsg)
231 print("@#$:" .. errmsg)
236 for k, v in pairs(tbl) do
242 function tset (parent, ...)
244 -- print ('set', ...)
246 local len = select ('#', ...)
247 local key, value = select (len-1, ...)
248 local cutpoint, cutkey
252 local key = select (i, ...)
253 local child = parent[key]
256 if child == nil then return
257 elseif next (child, next (child)) then cutpoint = nil cutkey = nil
258 elseif cutpoint == nil then cutpoint = parent cutkey = key end
260 elseif child == nil then child = {} parent[key] = child end
265 if value == nil and cutpoint then cutpoint[cutkey] = nil
266 else parent[key] = value return value end
270 function tget (parent, ...)
271 local len = select ('#', ...)
273 parent = parent[select (i, ...)]
274 if parent == nil then break end
280 local pager_lines = 23
281 local pager_printed = 0
282 local pager_skipping = false
283 local pager_filter_pipe = nil
285 function pager_reset()
287 pager_skipping = false
288 if pager_filter_pipe then
289 pager_filter_pipe:close()
290 pager_filter_pipe = nil
295 function print_more()
296 io.stdout:write(" --More-- ")
299 function print_nomore()
300 local bs = string.char(8)
301 local bs10 = bs .. bs .. bs .. bs .. bs .. bs .. bs .. bs .. bs .. bs
302 io.stdout:write(bs10 .. " " .. bs10)
305 function print_line(txt)
306 if pager_filter_pipe then
307 pager_filter_pipe:write(txt .. "\n")
310 if pager_printed >= pager_lines then
312 local ch = io.stdin:read(1)
315 elseif ch == "\n" then
316 pager_printed = pager_printed - 1
317 elseif ch == "q" then
319 pager_skipping = true
323 if not pager_skipping then
325 pager_printed = pager_printed + 1
331 function paged_write(text)
332 local t = readln.split(text, "[\n]")
333 if string.sub(text, -1) == "\n" then
336 for i, v in ipairs(t) do
340 if pager_filter_pipe then
341 pager_filter_pipe:write(v)
353 function get_choices(tbl, key)
355 for k, v in pairs(tbl) do
356 if string.sub(k, 1, #key) == key then
358 elseif 0 < #key and dotdotdot == k then
365 function get_exact_choice(choices, val)
366 local exact_idx = nil
367 local substr_idx = nil
368 local substr_seen = false
370 if #choices == 1 then
371 if choices[1] == dotdotdot then
373 elseif string.sub(choices[1], 1, #val) == val then
379 for i, v in ipairs(choices) do
383 elseif choices[i] ~= dotdotdot and string.sub(choices[i], 1, #val) == val then
390 elseif choices[i] == dotdotdot then
400 return exact_idx or substr_idx
403 function device_cli_help(rl)
404 local key = readln.split(rl.command, "[ ]+")
411 if ((#rl.command >= 1) and (string.sub(rl.command, -1) == " ")) or (#rl.command == 0) then
412 table.insert(key, "")
416 for i, v in ipairs(key) do
417 local choices = get_choices(tree, v)
418 local idx = get_exact_choice(choices, v)
420 local choice = choices[idx]
422 fullcmd = fullcmd .. choice .. " "
429 if i == #key and not error then
430 for j, w in ipairs(choices) do
432 paged_write(w .. "\t")
434 paged_write(" " .. w .. "\n")
446 function device_cli_tab_complete(rl)
447 local key = readln.split(rl.command, "[ ]+")
453 for i, v in ipairs(key) do
454 local choices = get_choices(tree, v)
455 local idx = get_exact_choice(choices, v)
456 if idx and choices[idx] ~= dotdotdot then
457 local choice = choices[idx]
459 -- print("level " .. i .. " '" .. choice .. "'")
460 fullcmd = fullcmd .. choice .. " "
462 -- print("level " .. i .. " : " .. table.concat(choices, " ") .. " ")
469 -- print("\n\nerror\n")
474 function device_cli_exec(rl)
476 local cmd_nopipe = rl.command
479 local pipe1, pipe2 = string.find(rl.command, "[|]")
481 cmd_nopipe = string.sub(rl.command, 1, pipe1-1)
482 cmd_pipe = string.sub(rl.command, pipe2+1, -1)
485 local key = readln.split(cmd_nopipe .. " <cr>", "[ ]+")
493 pager_filter_pipe = io.popen(cmd_pipe, "w")
499 for i, v in ipairs(key) do
500 local choices = get_choices(tree, v)
501 local idx = get_exact_choice(choices, v)
503 local choice = choices[idx]
507 if choice == dotdotdot then
508 -- keep the tree the same, update the choice value to match the input string
515 -- print("level " .. i .. " '" .. choice .. "'")
516 table.insert(rl.choices, choice)
518 -- print("level " .. i .. " : " .. table.concat(choices, " ") .. " ")
526 function populate_tree(commands)
529 for k, v in pairs(commands) do
530 local key = readln.split(k .. " <cr>", "[ ]+")
532 for i, kk in ipairs(key) do
533 if i == 1 and kk == "sh" then
537 if type(v) == "function" then
540 xtree[kk] = function(rl) paged_write(table.concat(v, "\n") .. "\n") end
543 if not xtree[kk] then
554 return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
558 function init_vpp(vpp)
559 local root_dir = "/home/ubuntu/vpp"
560 local pneum_path = root_dir .. "/build-root/install-vpp_lite_debug-native/vpp-api/lib64/libpneum.so"
562 vpp:init({ pneum_path = pneum_path })
564 vpp:init({ pneum_path = pneum_path })
565 vpp:json_api(root_dir .. "/build-root/install-vpp_lite_debug-native/vpp/vpp-api/vpe.api.json")
569 vpp:connect("lua_cli")
572 function run_cli(vpp, cli)
573 local reply = vpp:api_call("cli_inband", { cmd = cli })
574 if reply and #reply == 1 then
576 if 0 == rep.retval then
579 return "XXXXXLUACLI: API RETVAL ERROR : " .. tostring(rep.retval)
582 return "XXXXXLUACLI ERROR, RAW REPLY: " .. vpp.dump(reply)
587 function toprintablestring(s)
588 if type(s) == "string" then
589 return "\n"..vpp.hex_dump(s)
595 function interactive_cli(r)
598 local cmd = r.readln()
601 elseif cmd == "quit" or cmd == "exit" then
604 local func = device_cli_exec(r)
608 if trim(cmd) == "" then
610 for i = 1, #r.prompt do
613 paged_write("^\n% Invalid input detected at '^' marker.\n\n")
624 cmds_str = run_cli(vpp, "?")
625 vpp_cmds = readln.split(cmds_str, "\n")
628 for linenum, line in ipairs(vpp_cmds) do
629 local m,h = string.match(line, "^ (.-) (.*)$")
631 table.insert(vpp_clis, m)
632 device.output["vpp debug cli " .. m] = function(rl)
633 -- print("ARBITRARY CLI" .. vpp.dump(rl.choices))
634 print("LUACLI command: " .. table.concat(rl.choices, " "))
637 for i=4, #rl.choices -1 do
638 table.insert(sub, rl.choices[i])
640 local cli = table.concat(sub, " ")
641 print("Running CLI: " .. tostring(cli))
642 paged_write(run_cli(vpp, cli))
644 device.output["vpp debug cli " .. m .. " " .. dotdotdot] = function(rl)
648 local ret = run_cli(vpp, "help " .. m)
649 device.output["help vpp debug cli " .. m] = { ret }
653 for linenum, line in ipairs(vpp_clis) do
657 for msgnum, msgname in pairs(vpp.msg_number_to_name) do
658 local cli, numspaces = string.gsub(msgname, "_", " ")
659 device.output["call " .. cli .. " " .. dotdotdot] = function(rl)
662 device.output["call " .. cli] = function(rl)
663 print("LUACLI command: " .. table.concat(rl.choices, " "))
664 print("Running API: " .. msgname) -- vpp.dump(rl.choices))
669 for i=(1+1+numspaces+1), #rl.choices-1 do
670 -- print(i, rl.choices[i])
674 local fieldname = rl.choices[i]
675 local field = vpp.msg_name_to_fields[msgname][fieldname]
677 local s = rl.choices[i+1]
678 s=s:gsub("\\x(%x%x)",function (x) return string.char(tonumber(x,16)) end)
684 -- print("ARGS: ", vpp.dump(args))
685 local ret = vpp:api_call(msgname, args)
686 for i, reply in ipairs(ret) do
687 table.insert(out, "=================== Entry #" .. tostring(i))
688 for k, v in pairs(reply) do
689 table.insert(out, " " .. tostring(k) .. " : " .. toprintablestring(v))
692 -- paged_write(vpp.dump(ret) .. "\n\n")
693 paged_write(table.concat(out, "\n").."\n\n")
695 device.output["call " .. cli .. " help"] = function(rl)
697 for k, v in pairs(vpp.msg_name_to_fields[msgname]) do
698 table.insert(out, tostring(k) .. " : " .. v["ctype"] .. " ; " .. tostring(vpp.dump(v)) )
700 -- paged_write(vpp.dump(vpp.msg_name_to_fields[msgname]) .. "\n\n")
701 paged_write(table.concat(out, "\n").."\n\n")
703 -- vpp.msg_name_to_number = {}
708 local r = readln.reader()
711 r.prompt = "VPP(luaCLI)#"
713 r.help = device_cli_help
714 r.tab_complete = device_cli_tab_complete
715 print("===== CLI view, use ^D to end =====")
717 r.tree = populate_tree(device.output)
718 -- readln.pretty("xxxx", r.tree)
721 for idx, an_arg in ipairs(arg) do
723 if fname == "-i" then
727 pager_lines = 100000000
728 for line in io.lines(fname) do
730 local func = device_cli_exec(r)
739 print("You should specify '-i' as an argument for the interactive session,")
740 print("but with no other sources of commands, we start interactive session now anyway")