Luajit API and some examples

Change-Id: Ia140c4750f06870c40b7058c4afb2e20ca633a49
Signed-off-by: Andrew Yourtchenko <ayourtch@gmail.com>
diff --git a/vpp-api/lua/examples/cli/lua-cli.lua b/vpp-api/lua/examples/cli/lua-cli.lua
new file mode 100644
index 0000000..1ad5045
--- /dev/null
+++ b/vpp-api/lua/examples/cli/lua-cli.lua
@@ -0,0 +1,745 @@
+--[[
+/*
+ * 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()
+
+