summaryrefslogblamecommitdiffstats
path: root/mutt_lua.c
blob: 960ced82ce084a530aa93ec921b9635dd4e8cdb8 (plain) (tree)
1
2
3
4
5
6
7
8
9
   



                           
                                                       
                                                        
  
             



                                                                                
  



                                                                                
  

                                                                               

   
   
                                              



                           






                      
                   
                    
                   

                   
                    
                   
                  
                     
                       
                     
                     
                      
                    
 
                    
                                  

   
                                                     
   
                                             





                                            




                                                       
                                     
 
                                                                        
                                                           


                
 




                                                        
                                     
 
                                                                        
                                                           



                





                                                 
                                      
 
                                                

                                        
                         
                                   
             
 

                         
                                                      


              
                                        
           
   
                                                                     





                                          
                                                        
                                            

   
                                   

                  
                                 
 
                                               
   
                                                        
            


      
                                            
                      
        
           

   

                           
            

 





                                        
                                     
 
                                          
                                                        
 
                                    
                                                              
          
   














                                                                

   


                                    

                            
   
                    
                 
                    
                  
                 
                  
                  
                 
                   
     
                                              
                                          

                                                   
                                     
                                    
 
                                                                               
                              
                                        
                
            
     
                   
                 
     
                                                  
                                                                      
                                        
                
            
     
                 

                                                  
                                                                      

                                        
            
     
            
                                                                                              
              
            
   
 
                    
            
 
 





                                        
                                     

                                          
                                                        
 
                                                              

          
                                        








                                                           
                 
                    
                  
                  
                  


                   
                                          
                                                                 
                                        
       
                            
                  

       
                                            

                                          

                            
               
     
                 
                                                    

                   
                                                   

                 
                                           



                                                                                   
   

 





                                                   
                                       
 
                                                 
                                      
                                                
             
 
                              
   
                                                        
            


      
                                            
                      
        
           

   
             
                         
 
            

 




                                                  
                                         
 
                                                   
                                        
          
                            


           




                                               
                                       
 
                                                 
                                        
          
                          


           

                                                                       
                       

                             
                                                                       
 
                         
                                                                                           
                                 
                               

 
   
                                                     






                                      
                                           







                                  
                 
                    
  
 




                                                                        

                                          
                                               
                                  
                              








                                                                                                      

           
 



                                                               
                                      
 
                                                 
                                               



                                                              
                                 
   

 

                                    
                          
                          
   
                                   
 


                 

                
                                           

                       
          
   
                                                              

                 
 
                                
 


                                  



              
   



                                         
                                                               


   
                                                                                                  
   
                                                                       
                                                                    
 
                      
                                                              
 
                                       
   
                                                         
                                                                   
                                          
                         
                          
   
                                                       
                                             
                          

 
   
                                                                                                               
   
                                                                             
                                                                          
 
                                                  
 
                      
 
                              
 
                                                       
   
                                                       
                          
   
                  
   
                                                           
                            
   
                                               
                                       
 
                                  
   

                                                                                
                          
   
                          
 
/**
 * @file
 * Integrated Lua scripting
 *
 * @authors
 * Copyright (C) 2016 Richard Russon <rich@flatcap.org>
 * Copyright (C) 2016 Bernard Pratz <z+mutt+pub@m0g.net>
 *
 * @copyright
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @page neo_mutt_lua Integrated Lua scripting
 *
 * Integrated Lua scripting
 */

#ifndef LUA_COMPAT_ALL
#define LUA_COMPAT_ALL
#endif
#ifndef LUA_COMPAT_5_1
#define LUA_COMPAT_5_1
#endif

#include "config.h"
#include <lauxlib.h>
#include <limits.h>
#include <lua.h>
#include <lualib.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "mutt/lib.h"
#include "config/lib.h"
#include "core/lib.h"
#include "mutt_lua.h"
#include "parse/lib.h"
#include "muttlib.h"

/// Global Lua State
static lua_State *LuaState = NULL;

/**
 * LuaCommands - List of NeoMutt commands to register
 */
static const struct Command LuaCommands[] = {
  // clang-format off
  { "lua",        mutt_lua_parse,       0 },
  { "lua-source", mutt_lua_source_file, 0 },
  // clang-format on
};

/**
 * handle_panic - Handle a panic in the Lua interpreter
 * @param l Lua State
 * @retval -1 Always
 */
static int handle_panic(lua_State *l)
{
  mutt_debug(LL_DEBUG1, "lua runtime panic: %s\n", lua_tostring(l, -1));
  mutt_error("Lua runtime panic: %s", lua_tostring(l, -1));
  lua_pop(l, 1);
  return -1;
}

/**
 * handle_error - Handle an error in the Lua interpreter
 * @param l Lua State
 * @retval -1 Always
 */
static int handle_error(lua_State *l)
{
  mutt_debug(LL_DEBUG1, "lua runtime error: %s\n", lua_tostring(l, -1));
  mutt_error("Lua runtime error: %s", lua_tostring(l, -1));
  lua_pop(l, 1);
  return -1;
}

/**
 * lua_mutt_call - Call a NeoMutt command by name
 * @param l Lua State
 * @retval >=0 Success
 * @retval -1 Error
 */
static int lua_mutt_call(lua_State *l)
{
  mutt_debug(LL_DEBUG2, " * lua_mutt_call()\n");
  struct Buffer *err = buf_pool_get();
  struct Buffer *token = buf_pool_get();
  char buf[1024] = { 0 };
  const struct Command *cmd = NULL;
  int rc = 0;

  if (lua_gettop(l) == 0)
  {
    luaL_error(l, "Error command argument required.");
    return -1;
  }

  cmd = command_get(lua_tostring(l, 1));
  if (!cmd)
  {
    luaL_error(l, "Error command %s not found.", lua_tostring(l, 1));
    return -1;
  }

  for (int i = 2; i <= lua_gettop(l); i++)
  {
    const char *s = lua_tostring(l, i);
    mutt_strn_cat(buf, sizeof(buf), s, mutt_str_len(s));
    mutt_strn_cat(buf, sizeof(buf), " ", 1);
  }

  struct Buffer expn = buf_make(0);
  expn.data = buf;
  expn.dptr = buf;
  expn.dsize = mutt_str_len(buf);

  if (cmd->parse(token, &expn, cmd->data, err))
  {
    luaL_error(l, "NeoMutt error: %s", buf_string(err));
    rc = -1;
  }
  else
  {
    if (!lua_pushstring(l, buf_string(err)))
      handle_error(l);
    else
      rc++;
  }

  buf_pool_release(&token);
  buf_pool_release(&err);
  return rc;
}

/**
 * lua_mutt_set - Set a NeoMutt variable
 * @param l Lua State
 * @retval  0 Success
 * @retval -1 Error
 */
static int lua_mutt_set(lua_State *l)
{
  const char *param = lua_tostring(l, -2);
  mutt_debug(LL_DEBUG2, " * lua_mutt_set(%s)\n", param);

  struct Buffer err = buf_make(256);
  struct HashElem *he = cs_subset_lookup(NeoMutt->sub, param);
  if (!he)
  {
    // In case it is a my_var, we have to create it
    if (mutt_str_startswith(param, "my_"))
    {
      struct ConfigDef my_cdef = { 0 };
      my_cdef.name = param;
      my_cdef.type = DT_MYVAR;
      he = cs_create_variable(NeoMutt->sub->cs, &my_cdef, &err);
      if (!he)
        return -1;
    }
    else
    {
      luaL_error(l, "NeoMutt parameter not found %s", param);
      return -1;
    }
  }

  struct ConfigDef *cdef = he->data;

  int rc = 0;

  switch (DTYPE(cdef->type))
  {
    case DT_ADDRESS:
    case DT_ENUM:
    case DT_MBTABLE:
    case DT_MYVAR:
    case DT_PATH:
    case DT_REGEX:
    case DT_SLIST:
    case DT_SORT:
    case DT_STRING:
    {
      const char *value = lua_tostring(l, -1);
      size_t val_size = lua_rawlen(l, -1);
      struct Buffer value_buf = buf_make(val_size);
      buf_strcpy_n(&value_buf, value, val_size);
      if (DTYPE(he->type) == DT_PATH)
        buf_expand_path(&value_buf);

      int rv = cs_subset_he_string_set(NeoMutt->sub, he, value_buf.data, &err);
      buf_dealloc(&value_buf);
      if (CSR_RESULT(rv) != CSR_SUCCESS)
        rc = -1;
      break;
    }
    case DT_NUMBER:
    case DT_QUAD:
    {
      const intptr_t value = lua_tointeger(l, -1);
      int rv = cs_subset_he_native_set(NeoMutt->sub, he, value, &err);
      if (CSR_RESULT(rv) != CSR_SUCCESS)
        rc = -1;
      break;
    }
    case DT_BOOL:
    {
      const intptr_t value = lua_toboolean(l, -1);
      int rv = cs_subset_he_native_set(NeoMutt->sub, he, value, &err);
      if (CSR_RESULT(rv) != CSR_SUCCESS)
        rc = -1;
      break;
    }
    default:
      luaL_error(l, "Unsupported NeoMutt parameter type %d for %s", DTYPE(cdef->type), param);
      rc = -1;
      break;
  }

  buf_dealloc(&err);
  return rc;
}

/**
 * lua_mutt_get - Get a NeoMutt variable
 * @param l Lua State
 * @retval  1 Success
 * @retval -1 Error
 */
static int lua_mutt_get(lua_State *l)
{
  const char *param = lua_tostring(l, -1);
  mutt_debug(LL_DEBUG2, " * lua_mutt_get(%s)\n", param);

  struct HashElem *he = cs_subset_lookup(NeoMutt->sub, param);
  if (!he)
  {
    mutt_debug(LL_DEBUG2, " * error\n");
    luaL_error(l, "NeoMutt parameter not found %s", param);
    return -1;
  }

  struct ConfigDef *cdef = he->data;

  switch (DTYPE(cdef->type))
  {
    case DT_ADDRESS:
    case DT_ENUM:
    case DT_MBTABLE:
    case DT_MYVAR:
    case DT_REGEX:
    case DT_SLIST:
    case DT_SORT:
    case DT_STRING:
    {
      struct Buffer value = buf_make(256);
      int rc = cs_subset_he_string_get(NeoMutt->sub, he, &value);
      if (CSR_RESULT(rc) != CSR_SUCCESS)
      {
        buf_dealloc(&value);
        return -1;
      }

      struct Buffer escaped = buf_make(256);
      escape_string(&escaped, value.data);
      lua_pushstring(l, escaped.data);
      buf_dealloc(&value);
      buf_dealloc(&escaped);
      return 1;
    }
    case DT_QUAD:
      lua_pushinteger(l, (unsigned char) cdef->var);
      return 1;
    case DT_NUMBER:
      lua_pushinteger(l, (signed short) cdef->var);
      return 1;
    case DT_BOOL:
      lua_pushboolean(l, (bool) cdef->var);
      return 1;
    default:
      luaL_error(l, "NeoMutt parameter type %d unknown for %s", cdef->type, param);
      return -1;
  }
}

/**
 * lua_mutt_enter - Execute NeoMutt config from Lua
 * @param l Lua State
 * @retval >=0 Success
 * @retval -1  Error
 */
static int lua_mutt_enter(lua_State *l)
{
  mutt_debug(LL_DEBUG2, " * lua_mutt_enter()\n");
  struct Buffer *err = buf_pool_get();
  char *buf = mutt_str_dup(lua_tostring(l, -1));
  int rc = 0;

  if (parse_rc_line(buf, err))
  {
    luaL_error(l, "NeoMutt error: %s", buf_string(err));
    rc = -1;
  }
  else
  {
    if (!lua_pushstring(l, buf_string(err)))
      handle_error(l);
    else
      rc++;
  }

  FREE(&buf);
  buf_pool_release(&err);

  return rc;
}

/**
 * lua_mutt_message - Display a message in Neomutt
 * @param l Lua State
 * @retval 0 Always
 */
static int lua_mutt_message(lua_State *l)
{
  mutt_debug(LL_DEBUG2, " * lua_mutt_message()\n");
  const char *msg = lua_tostring(l, -1);
  if (msg)
    mutt_message("%s", msg);
  return 0;
}

/**
 * lua_mutt_error - Display an error in Neomutt
 * @param l Lua State
 * @retval 0 Always
 */
static int lua_mutt_error(lua_State *l)
{
  mutt_debug(LL_DEBUG2, " * lua_mutt_error()\n");
  const char *msg = lua_tostring(l, -1);
  if (msg)
    mutt_error("%s", msg);
  return 0;
}

/**
 * lua_expose_command - Expose a NeoMutt command to the Lua interpreter
 * @param l   Lua state
 * @param cmd NeoMutt Command
 */
static void lua_expose_command(lua_State *l, const struct Command *cmd)
{
  char buf[1024] = { 0 };
  snprintf(buf, sizeof(buf), "mutt.command.%s = function (...); mutt.call('%s', ...); end",
           cmd->name, cmd->name);
  (void) luaL_dostring(l, buf);
}

/**
 * LuaMuttCommands - List of Lua commands to register
 *
 * In NeoMutt, run:
 *
 * `:lua mutt.message('hello')`
 *
 * and it will call lua_mutt_message()
 */
static const luaL_Reg LuaMuttCommands[] = {
  // clang-format off
  { "set",     lua_mutt_set },
  { "get",     lua_mutt_get },
  { "call",    lua_mutt_call },
  { "enter",   lua_mutt_enter },
  { "print",   lua_mutt_message },
  { "message", lua_mutt_message },
  { "error",   lua_mutt_error },
  { NULL, NULL },
  // clang-format on
};

/**
 * luaopen_mutt_decl - Declare some NeoMutt types to the Lua interpreter
 * @param l Lua State
 * @retval 1 Always
 */
static int luaopen_mutt_decl(lua_State *l)
{
  mutt_debug(LL_DEBUG2, " * luaopen_mutt()\n");
  luaL_newlib(l, LuaMuttCommands);
  int lib_idx = lua_gettop(l);

  // clang-format off
  lua_pushstring(l, "VERSION");     lua_pushstring(l, mutt_make_version()); lua_settable(l, lib_idx);;
  lua_pushstring(l, "QUAD_YES");    lua_pushinteger(l, MUTT_YES);           lua_settable(l, lib_idx);;
  lua_pushstring(l, "QUAD_NO");     lua_pushinteger(l, MUTT_NO);            lua_settable(l, lib_idx);;
  lua_pushstring(l, "QUAD_ASKYES"); lua_pushinteger(l, MUTT_ASKYES);        lua_settable(l, lib_idx);;
  lua_pushstring(l, "QUAD_ASKNO");  lua_pushinteger(l, MUTT_ASKNO);         lua_settable(l, lib_idx);;
  // clang-format on

  return 1;
}

/**
 * luaopen_mutt - Expose a 'Mutt' object to the Lua interpreter
 * @param l Lua State
 */
static void luaopen_mutt(lua_State *l)
{
  luaL_requiref(l, "mutt", luaopen_mutt_decl, 1);
  (void) luaL_dostring(l, "mutt.command = {}");

  struct Command *c = NULL;
  for (size_t i = 0, size = commands_array(&c); i < size; i++)
  {
    lua_expose_command(l, &c[i]);
  }
}

/**
 * lua_init - Initialise a Lua State
 * @param[out] l Lua State
 * @retval true Successful
 */
static bool lua_init(lua_State **l)
{
  if (!l)
    return false;
  if (*l)
    return true;

  mutt_debug(LL_DEBUG2, " * lua_init()\n");
  *l = luaL_newstate();

  if (!*l)
  {
    mutt_error(_("Error: Couldn't load the lua interpreter"));
    return false;
  }

  lua_atpanic(*l, handle_panic);

  /* load various Lua libraries */
  luaL_openlibs(*l);
  luaopen_mutt(*l);

  return true;
}

/**
 * mutt_lua_init - Setup feature commands
 */
void mutt_lua_init(void)
{
  commands_register(LuaCommands, mutt_array_size(LuaCommands));
}

/**
 * mutt_lua_parse - Parse the 'lua' command - Implements Command::parse() - @ingroup command_parse
 */
enum CommandResult mutt_lua_parse(struct Buffer *buf, struct Buffer *s,
                                  intptr_t data, struct Buffer *err)
{
  lua_init(&LuaState);
  mutt_debug(LL_DEBUG2, " * mutt_lua_parse(%s)\n", buf->data);

  if (luaL_dostring(LuaState, s->dptr))
  {
    mutt_debug(LL_DEBUG2, " * %s -> failure\n", s->dptr);
    buf_printf(err, "%s: %s", s->dptr, lua_tostring(LuaState, -1));
    /* pop error message from the stack */
    lua_pop(LuaState, 1);
    return MUTT_CMD_ERROR;
  }
  mutt_debug(LL_DEBUG2, " * %s -> success\n", s->dptr);
  buf_reset(s); // Clear the rest of the line
  return MUTT_CMD_SUCCESS;
}

/**
 * mutt_lua_source_file - Parse the 'lua-source' command - Implements Command::parse() - @ingroup command_parse
 */
enum CommandResult mutt_lua_source_file(struct Buffer *buf, struct Buffer *s,
                                        intptr_t data, struct Buffer *err)
{
  mutt_debug(LL_DEBUG2, " * mutt_lua_source()\n");

  lua_init(&LuaState);

  char path[PATH_MAX] = { 0 };

  if (parse_extract_token(buf, s, TOKEN_NO_FLAGS) != 0)
  {
    buf_printf(err, _("source: error at %s"), s->dptr);
    return MUTT_CMD_ERROR;
  }
  if (MoreArgs(s))
  {
    buf_printf(err, _("%s: too many arguments"), "source");
    return MUTT_CMD_WARNING;
  }
  mutt_str_copy(path, buf->data, sizeof(path));
  mutt_expand_path(path, sizeof(path));

  if (luaL_dofile(LuaState, path))
  {
    mutt_error(_("Couldn't source lua source: %s"), lua_tostring(LuaState, -1));
    lua_pop(LuaState, 1);
    return MUTT_CMD_ERROR;
  }
  return MUTT_CMD_SUCCESS;
}