b3a24d7d0ef92f3ebb6a4f54387c640cda333d44
[vpp.git] / vpp-api / lua / examples / cli / lua-cli.lua
1 --[[
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
23 vpp = require "vpp-lapi"
24
25
26 local dotdotdot = "..."
27
28 -- First the "readline" routine
29
30 readln = {
31 split = 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
50 end,
51
52 reader = 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
187 end
188
189 }
190
191 --[[
192
193 r = reader()
194
195 local done = false
196
197 while 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
203 end
204
205 r.done()
206
207 ]]
208
209 --------- MDS show tech parser
210
211 local print_section = nil
212 local list_sections = false
213
214 local curr_section = "---"
215 local curr_parser = nil
216
217 -- by default operate in batch mode
218 local batch_mode = true
219
220 local db = {}
221 local device = {}
222 device.output = {}
223 local seen_section = {}
224
225 function start_collection(name)
226   device = {}
227   seen_section = {}
228 end
229
230 function print_error(errmsg)
231   print("@#$:" .. errmsg)
232 end
233
234 function keys(tbl)
235   local t = {}
236   for k, v in pairs(tbl) do
237     table.insert(t, k)
238   end
239   return t
240 end
241
242 function 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
270 function 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
280 local pager_lines = 23
281 local pager_printed = 0
282 local pager_skipping = false
283 local pager_filter_pipe = nil
284
285 function 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
292 end
293
294
295 function print_more()
296   io.stdout:write(" --More-- ")
297 end
298
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)
303 end
304
305 function 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
329 end
330
331 function 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
347 end
348
349
350
351
352
353 function 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
363 end
364
365 function 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
401 end
402
403 function 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()
444 end
445
446 function 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()
472 end
473
474 function 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
524 end
525
526 function 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
551 end
552
553 function trim (s)
554   return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
555 end
556
557
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"
561
562   vpp:init({ pneum_path = pneum_path })
563
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")
566
567
568
569   vpp:connect("lua_cli")
570 end
571
572 function 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
584 end
585
586
587 function toprintablestring(s)
588   if type(s) == "string" then
589     return "\n"..vpp.hex_dump(s)
590   else
591     return tostring(s)
592   end
593 end
594
595 function 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
618 end
619
620 device = {}
621 device.output = {}
622
623 init_vpp(vpp)
624 cmds_str = run_cli(vpp, "?")
625 vpp_cmds = readln.split(cmds_str, "\n")
626 vpp_clis = {}
627
628 for 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
651 end
652
653 for linenum, line in ipairs(vpp_clis) do
654   -- print(line, ret)
655 end
656
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)
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 = {}
704 end
705
706
707
708 local r = readln.reader()
709 local done = false
710
711 r.prompt = "VPP(luaCLI)#"
712
713 r.help = device_cli_help
714 r.tab_complete = device_cli_tab_complete
715 print("===== CLI view, use ^D to end =====")
716
717 r.tree = populate_tree(device.output)
718 -- readln.pretty("xxxx", r.tree)
719
720
721 for 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
736 end
737
738 if #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)
742 end
743
744 vpp:disconnect()
745 r.done()
746
747