blob: b3a24d7d0ef92f3ebb6a4f54387c640cda333d44 [file] [log] [blame]
Andrew Yourtchenkofa1456a2016-11-11 16:32:52 +00001--[[
2/*
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:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16]]
17
18-- Experimental prototype CLI using API to VPP, with tab completion
19--
20-- Written by Andrew Yourtchenko (ayourtch@cisco.com) 2010,2016
21--
22
23vpp = require "vpp-lapi"
24
25
26local dotdotdot = "..."
27
28-- First the "readline" routine
29
30readln = {
31split = function(str, pat)
32 local t = {} -- NOTE: use {n = 0} in Lua-5.0
33 local fpat = "(.-)" .. pat
34 local last_end = 1
35 if str then
36 local s, e, cap = str:find(fpat, 1)
37 while s do
38 if s ~= 1 or cap ~= "" then
39 table.insert(t,cap)
40 end
41 last_end = e+1
42 s, e, cap = str:find(fpat, last_end)
43 end
44 if last_end <= #str then
45 cap = str:sub(last_end)
46 table.insert(t, cap)
47 end
48 end
49 return t
50end,
51
52reader = function()
53 local rl = {}
54
55 rl.init = function()
56 os.execute("stty -icanon min 1 -echo")
57 rl.rawmode = true
58 end
59
60 rl.done = function()
61 os.execute("stty icanon echo")
62 rl.rawmode = false
63 end
64
65 rl.prompt = ">"
66 rl.history = { "" }
67 rl.history_index = 1
68 rl.history_length = 1
69
70 rl.hide_cmd = function()
71 local bs = string.char(8) .. " " .. string.char(8)
72 for i = 1, #rl.command do
73 io.stdout:write(bs)
74 end
75 end
76
77 rl.show_cmd = function()
78 if rl.command then
79 io.stdout:write(rl.command)
80 end
81 end
82
83 rl.store_history = function(cmd)
84 if cmd == "" then
85 return
86 end
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] = ""
91 end
92
93 rl.readln = function()
94 local done = false
95 local need_prompt = true
96 rl.command = ""
97
98 if not rl.rawmode then
99 rl.init()
100 end
101
102 while not done do
103 if need_prompt then
104 io.stdout:write(rl.prompt)
105 io.stdout:write(rl.command)
106 need_prompt = false
107 end
108
109 local ch = io.stdin:read(1)
110 if ch:byte(1) == 27 then
111 -- CONTROL
112 local ch2 = io.stdin:read(1)
113 -- arrows
114 if ch2:byte(1) == 91 then
115 local ch3 = io.stdin:read(1)
116 local b = ch3:byte(1)
117 if b == 65 then
118 ch = "UP"
119 elseif b == 66 then
120 ch = "DOWN"
121 elseif b == 67 then
122 ch = "RIGHT"
123 elseif b == 68 then
124 ch = "LEFT"
125 end
126 -- print("Byte: " .. ch3:byte(1))
127 -- if ch3:byte(1)
128 end
129 end
130
131 if ch == "?" then
132 io.stdout:write(ch)
133 io.stdout:write("\n")
134 if rl.help then
135 rl.help(rl)
136 end
137 need_prompt = true
138 elseif ch == "\t" then
139 if rl.tab_complete then
140 rl.tab_complete(rl)
141 end
142 io.stdout:write("\n")
143 need_prompt = true
144 elseif ch == "\n" then
145 io.stdout:write(ch)
146 done = true
147 elseif ch == "\004" then
148 io.stdout:write("\n")
149 rl.command = nil
150 done = true
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)
155 end
156 elseif #ch > 1 then
157 -- control char
158 if ch == "UP" then
159 rl.hide_cmd()
160 if rl.history_index == #rl.history then
161 rl.history[rl.history_index] = rl.command
162 end
163 if rl.history_index > 1 then
164 rl.history_index = rl.history_index - 1
165 rl.command = rl.history[rl.history_index]
166 end
167 rl.show_cmd()
168 elseif ch == "DOWN" then
169 rl.hide_cmd()
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]
173 end
174 rl.show_cmd()
175 end
176 else
177 io.stdout:write(ch)
178 rl.command = rl.command .. ch
179 end
180 end
181 if rl.command then
182 rl.store_history(rl.command)
183 end
184 return rl.command
185 end
186 return rl
187end
188
189}
190
191--[[
192
193r = reader()
194
195local done = false
196
197while not done do
198 local cmd = r.readln()
199 print("Command: " .. tostring(cmd))
200 if not cmd or cmd == "quit" then
201 done = true
202 end
203end
204
205r.done()
206
207]]
208
209--------- MDS show tech parser
210
211local print_section = nil
212local list_sections = false
213
214local curr_section = "---"
215local curr_parser = nil
216
217-- by default operate in batch mode
218local batch_mode = true
219
220local db = {}
221local device = {}
222device.output = {}
223local seen_section = {}
224
225function start_collection(name)
226 device = {}
227 seen_section = {}
228end
229
230function print_error(errmsg)
231 print("@#$:" .. errmsg)
232end
233
234function keys(tbl)
235 local t = {}
236 for k, v in pairs(tbl) do
237 table.insert(t, k)
238 end
239 return t
240end
241
242function tset (parent, ...)
243
244 -- print ('set', ...)
245
246 local len = select ('#', ...)
247 local key, value = select (len-1, ...)
248 local cutpoint, cutkey
249
250 for i=1,len-2 do
251
252 local key = select (i, ...)
253 local child = parent[key]
254
255 if value == nil then
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
259
260 elseif child == nil then child = {} parent[key] = child end
261
262 parent = child
263 end
264
265 if value == nil and cutpoint then cutpoint[cutkey] = nil
266 else parent[key] = value return value end
267 end
268
269
270function tget (parent, ...)
271 local len = select ('#', ...)
272 for i=1,len do
273 parent = parent[select (i, ...)]
274 if parent == nil then break end
275 end
276 return parent
277 end
278
279
280local pager_lines = 23
281local pager_printed = 0
282local pager_skipping = false
283local pager_filter_pipe = nil
284
285function pager_reset()
286 pager_printed = 0
287 pager_skipping = false
288 if pager_filter_pipe then
289 pager_filter_pipe:close()
290 pager_filter_pipe = nil
291 end
292end
293
294
295function print_more()
296 io.stdout:write(" --More-- ")
297end
298
299function 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)
303end
304
305function print_line(txt)
306 if pager_filter_pipe then
307 pager_filter_pipe:write(txt .. "\n")
308 return
309 end
310 if pager_printed >= pager_lines then
311 print_more()
312 local ch = io.stdin:read(1)
313 if ch == " " then
314 pager_printed = 0
315 elseif ch == "\n" then
316 pager_printed = pager_printed - 1
317 elseif ch == "q" then
318 pager_printed = 0
319 pager_skipping = true
320 end
321 print_nomore()
322 end
323 if not pager_skipping then
324 print(txt)
325 pager_printed = pager_printed + 1
326 else
327 -- skip printing
328 end
329end
330
331function paged_write(text)
332 local t = readln.split(text, "[\n]")
333 if string.sub(text, -1) == "\n" then
334 table.insert(t, "")
335 end
336 for i, v in ipairs(t) do
337 if i < #t then
338 print_line(v)
339 else
340 if pager_filter_pipe then
341 pager_filter_pipe:write(v)
342 else
343 io.stdout:write(v)
344 end
345 end
346 end
347end
348
349
350
351
352
353function get_choices(tbl, key)
354 local res = {}
355 for k, v in pairs(tbl) do
356 if string.sub(k, 1, #key) == key then
357 table.insert(res, k)
358 elseif 0 < #key and dotdotdot == k then
359 table.insert(res, k)
360 end
361 end
362 return res
363end
364
365function get_exact_choice(choices, val)
366 local exact_idx = nil
367 local substr_idx = nil
368 local substr_seen = false
369
370 if #choices == 1 then
371 if choices[1] == dotdotdot then
372 return 1
373 elseif string.sub(choices[1], 1, #val) == val then
374 return 1
375 else
376 return nil
377 end
378 else
379 for i, v in ipairs(choices) do
380 if v == val then
381 exact_idx = i
382 substr_seen = true
383 elseif choices[i] ~= dotdotdot and string.sub(choices[i], 1, #val) == val then
384 if substr_seen then
385 substr_idx = nil
386 else
387 substr_idx = i
388 substr_seen = true
389 end
390 elseif choices[i] == dotdotdot then
391 if substr_seen then
392 substr_idx = nil
393 else
394 substr_idx = i
395 substr_seen = true
396 end
397 end
398 end
399 end
400 return exact_idx or substr_idx
401end
402
403function device_cli_help(rl)
404 local key = readln.split(rl.command, "[ ]+")
405 local tree = rl.tree
406 local keylen = #key
407 local fullcmd = ""
408 local error = false
409 local terse = true
410
411 if ((#rl.command >= 1) and (string.sub(rl.command, -1) == " ")) or (#rl.command == 0) then
412 table.insert(key, "")
413 terse = false
414 end
415
416 for i, v in ipairs(key) do
417 local choices = get_choices(tree, v)
418 local idx = get_exact_choice(choices, v)
419 if idx then
420 local choice = choices[idx]
421 tree = tree[choice]
422 fullcmd = fullcmd .. choice .. " "
423 else
424 if i < #key then
425 error = true
426 end
427 end
428
429 if i == #key and not error then
430 for j, w in ipairs(choices) do
431 if terse then
432 paged_write(w .. "\t")
433 else
434 paged_write(" " .. w .. "\n")
435 end
436 end
437 paged_write("\n")
438 if terse then
439 paged_write(" \n")
440 end
441 end
442 end
443 pager_reset()
444end
445
446function device_cli_tab_complete(rl)
447 local key = readln.split(rl.command, "[ ]+")
448 local tree = rl.tree
449 local keylen = #key
450 local fullcmd = ""
451 local error = false
452
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]
458 tree = tree[choice]
459 -- print("level " .. i .. " '" .. choice .. "'")
460 fullcmd = fullcmd .. choice .. " "
461 else
462 -- print("level " .. i .. " : " .. table.concat(choices, " ") .. " ")
463 error = true
464 end
465 end
466 if not error then
467 rl.command = fullcmd
468 else
469 -- print("\n\nerror\n")
470 end
471 pager_reset()
472end
473
474function device_cli_exec(rl)
475
476 local cmd_nopipe = rl.command
477 local cmd_pipe = nil
478
479 local pipe1, pipe2 = string.find(rl.command, "[|]")
480 if pipe1 then
481 cmd_nopipe = string.sub(rl.command, 1, pipe1-1)
482 cmd_pipe = string.sub(rl.command, pipe2+1, -1)
483 end
484
485 local key = readln.split(cmd_nopipe .. " <cr>", "[ ]+")
486 local tree = rl.tree
487 local keylen = #key
488 local fullcmd = ""
489 local error = false
490 local func = nil
491
492 if cmd_pipe then
493 pager_filter_pipe = io.popen(cmd_pipe, "w")
494 end
495
496
497 rl.choices = {}
498
499 for i, v in ipairs(key) do
500 local choices = get_choices(tree, v)
501 local idx = get_exact_choice(choices, v)
502 if idx then
503 local choice = choices[idx]
504 if i == #key then
505 func = tree[choice]
506 else
507 if choice == dotdotdot then
508 -- keep the tree the same, update the choice value to match the input string
509 choices[idx] = v
510 choice = v
511 else
512 tree = tree[choice]
513 end
514 end
515 -- print("level " .. i .. " '" .. choice .. "'")
516 table.insert(rl.choices, choice)
517 else
518 -- print("level " .. i .. " : " .. table.concat(choices, " ") .. " ")
519 error = true
520 return nil
521 end
522 end
523 return func
524end
525
526function populate_tree(commands)
527 local tree = {}
528
529 for k, v in pairs(commands) do
530 local key = readln.split(k .. " <cr>", "[ ]+")
531 local xtree = tree
532 for i, kk in ipairs(key) do
533 if i == 1 and kk == "sh" then
534 kk = "show"
535 end
536 if i == #key then
537 if type(v) == "function" then
538 xtree[kk] = v
539 else
540 xtree[kk] = function(rl) paged_write(table.concat(v, "\n") .. "\n") end
541 end
542 else
543 if not xtree[kk] then
544 xtree[kk] = {}
545 end
546 xtree = xtree[kk]
547 end
548 end
549 end
550 return tree
551end
552
553function trim (s)
554 return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
555end
556
557
558function init_vpp(vpp)
559 local root_dir = "/home/ubuntu/vpp"
Andrew Yourtchenkob868e4e2016-12-08 14:03:55 +0000560 local pneum_path = root_dir .. "/build-root/install-vpp_lite_debug-native/vpp-api/lib64/libpneum.so"
Andrew Yourtchenkofa1456a2016-11-11 16:32:52 +0000561
562 vpp:init({ pneum_path = pneum_path })
563
Andrew Yourtchenkob868e4e2016-12-08 14:03:55 +0000564 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")
566
567
Andrew Yourtchenkofa1456a2016-11-11 16:32:52 +0000568
569 vpp:connect("lua_cli")
570end
571
572function run_cli(vpp, cli)
573 local reply = vpp:api_call("cli_inband", { cmd = cli })
574 if reply and #reply == 1 then
575 local rep = reply[1]
576 if 0 == rep.retval then
577 return rep.reply
578 else
579 return "XXXXXLUACLI: API RETVAL ERROR : " .. tostring(rep.retval)
580 end
581 else
582 return "XXXXXLUACLI ERROR, RAW REPLY: " .. vpp.dump(reply)
583 end
584end
585
586
587function toprintablestring(s)
588 if type(s) == "string" then
589 return "\n"..vpp.hex_dump(s)
590 else
591 return tostring(s)
592 end
593end
594
595function interactive_cli(r)
596 while not done do
597 pager_reset()
598 local cmd = r.readln()
599 if not cmd then
600 done = true
601 elseif cmd == "quit" or cmd == "exit" then
602 done = true
603 else
604 local func = device_cli_exec(r)
605 if func then
606 func(r)
607 else
608 if trim(cmd) == "" then
609 else
610 for i = 1, #r.prompt do
611 paged_write(" ")
612 end
613 paged_write("^\n% Invalid input detected at '^' marker.\n\n")
614 end
615 end
616 end
617 end
618end
619
620device = {}
621device.output = {}
622
623init_vpp(vpp)
624cmds_str = run_cli(vpp, "?")
625vpp_cmds = readln.split(cmds_str, "\n")
626vpp_clis = {}
627
628for linenum, line in ipairs(vpp_cmds) do
629 local m,h = string.match(line, "^ (.-) (.*)$")
630 if m and #m > 0 then
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, " "))
635 local sub = {}
636 --
637 for i=4, #rl.choices -1 do
638 table.insert(sub, rl.choices[i])
639 end
640 local cli = table.concat(sub, " ")
641 print("Running CLI: " .. tostring(cli))
642 paged_write(run_cli(vpp, cli))
643 end
644 device.output["vpp debug cli " .. m .. " " .. dotdotdot] = function(rl)
645 print("ARGH")
646 end
647
648 local ret = run_cli(vpp, "help " .. m)
649 device.output["help vpp debug cli " .. m] = { ret }
650 end
651end
652
653for linenum, line in ipairs(vpp_clis) do
654 -- print(line, ret)
655end
656
Andrew Yourtchenkob868e4e2016-12-08 14:03:55 +0000657for msgnum, msgname in pairs(vpp.msg_number_to_name) do
Andrew Yourtchenkofa1456a2016-11-11 16:32:52 +0000658 local cli, numspaces = string.gsub(msgname, "_", " ")
659 device.output["call " .. cli .. " " .. dotdotdot] = function(rl)
660 print("ARGH")
661 end
662 device.output["call " .. cli] = function(rl)
663 print("LUACLI command: " .. table.concat(rl.choices, " "))
664 print("Running API: " .. msgname) -- vpp.dump(rl.choices))
665 local out = {}
666 local args = {}
667 local ntaken = 0
668 local argname = ""
669 for i=(1+1+numspaces+1), #rl.choices-1 do
670 -- print(i, rl.choices[i])
671 if ntaken > 0 then
672 ntaken = ntaken -1
673 else
674 local fieldname = rl.choices[i]
675 local field = vpp.msg_name_to_fields[msgname][fieldname]
676 if field then
677 local s = rl.choices[i+1]
678 s=s:gsub("\\x(%x%x)",function (x) return string.char(tonumber(x,16)) end)
679 args[fieldname] = s
680 ntaken = 1
681 end
682 end
683 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))
690 end
691 end
692 -- paged_write(vpp.dump(ret) .. "\n\n")
693 paged_write(table.concat(out, "\n").."\n\n")
694 end
695 device.output["call " .. cli .. " help"] = function(rl)
696 local out = {}
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)) )
699 end
700 -- paged_write(vpp.dump(vpp.msg_name_to_fields[msgname]) .. "\n\n")
701 paged_write(table.concat(out, "\n").."\n\n")
702 end
703-- vpp.msg_name_to_number = {}
704end
705
706
707
708local r = readln.reader()
709local done = false
710
711r.prompt = "VPP(luaCLI)#"
712
713r.help = device_cli_help
714r.tab_complete = device_cli_tab_complete
715print("===== CLI view, use ^D to end =====")
716
717r.tree = populate_tree(device.output)
718-- readln.pretty("xxxx", r.tree)
719
720
721for idx, an_arg in ipairs(arg) do
722 local fname = an_arg
723 if fname == "-i" then
724 pager_lines = 23
725 interactive_cli(r)
726 else
727 pager_lines = 100000000
728 for line in io.lines(fname) do
729 r.command = line
730 local func = device_cli_exec(r)
731 if func then
732 func(r)
733 end
734 end
735 end
736end
737
738if #arg == 0 then
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")
741 interactive_cli(r)
742end
743
744vpp:disconnect()
745r.done()
746
747