NeoMutt  2025-12-11-911-gd8d604
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
lua.c
Go to the documentation of this file.
1
27
33
34#ifndef LUA_COMPAT_ALL
35#define LUA_COMPAT_ALL
36#endif
37#ifndef LUA_COMPAT_5_1
38#define LUA_COMPAT_5_1
39#endif
40
41#include "config.h"
42#include <lauxlib.h>
43#include <lua.h>
44#include <lualib.h>
45#include <stdbool.h>
46#include <stdint.h>
47#include <stdio.h>
48#include "mutt/lib.h"
49#include "config/lib.h"
50#include "core/lib.h"
51#include "parse/lib.h"
52#include "muttlib.h"
53#include "version.h"
54
60static int lua_handle_panic(lua_State *l)
61{
62 mutt_debug(LL_DEBUG1, "lua runtime panic: %s\n", lua_tostring(l, -1));
63 mutt_error("Lua runtime panic: %s", lua_tostring(l, -1));
64 lua_pop(l, 1);
65 return -1;
66}
67
73static int lua_handle_error(lua_State *l)
74{
75 mutt_debug(LL_DEBUG1, "lua runtime error: %s\n", lua_tostring(l, -1));
76 mutt_error("Lua runtime error: %s", lua_tostring(l, -1));
77 lua_pop(l, 1);
78 return -1;
79}
80
87static int lua_cb_global_call(lua_State *l)
88{
89 mutt_debug(LL_DEBUG2, "enter\n");
90 struct Buffer *buf = buf_pool_get();
91 struct ParseContext *pc = parse_context_new();
92 struct ParseError *pe = parse_error_new();
93 const struct Command *cmd = NULL;
94 int rc = 0;
95
96 if (lua_gettop(l) == 0)
97 {
98 buf_pool_release(&buf);
100 parse_error_free(&pe);
101 luaL_error(l, "Error command argument required");
102 return -1;
103 }
104
105 cmd = commands_get(&NeoMutt->commands, lua_tostring(l, 1));
106 if (!cmd)
107 {
108 buf_pool_release(&buf);
110 parse_error_free(&pe);
111 luaL_error(l, "Error command %s not found", lua_tostring(l, 1));
112 return -1;
113 }
114
115 for (int i = 2; i <= lua_gettop(l); i++)
116 {
117 buf_addstr(buf, lua_tostring(l, i));
118 buf_addch(buf, ' ');
119 }
120 buf_seek(buf, 0);
121
122 if (cmd->parse(cmd, buf, pc, pe))
123 {
124 luaL_error(l, "NeoMutt error: %s", buf_string(pe->message));
125 rc = -1;
126 }
127 else
128 {
129 if (!lua_pushstring(l, buf_string(pe->message)))
131 else
132 rc++;
133 }
134
135 buf_pool_release(&buf);
137 parse_error_free(&pe);
138 return rc;
139}
140
147static int lua_cb_global_set(lua_State *l)
148{
149 const char *param = lua_tostring(l, -2);
150 mutt_debug(LL_DEBUG2, "%s\n", param);
151
152 struct Buffer *err = buf_pool_get();
153 struct HashElem *he = cs_subset_lookup(NeoMutt->sub, param);
154 if (!he)
155 {
156 // In case it is a my_var, we have to create it
157 if (mutt_str_startswith(param, "my_"))
158 {
159 struct ConfigDef my_cdef = { 0 };
160 my_cdef.name = param;
161 my_cdef.type = DT_MYVAR;
162 he = cs_create_variable(NeoMutt->sub->cs, &my_cdef, err);
163 if (!he)
164 return -1;
165 }
166 else
167 {
168 luaL_error(l, "NeoMutt parameter not found %s", param);
169 return -1;
170 }
171 }
172
173 struct ConfigDef *cdef = he->data;
174
175 int rc = 0;
176
177 switch (CONFIG_TYPE(cdef->type))
178 {
179 case DT_ADDRESS:
180 case DT_ENUM:
181 case DT_EXPANDO:
182 case DT_MBTABLE:
183 case DT_MYVAR:
184 case DT_PATH:
185 case DT_REGEX:
186 case DT_SLIST:
187 case DT_SORT:
188 case DT_STRING:
189 {
190 const char *value = lua_tostring(l, -1);
191 size_t val_size = lua_rawlen(l, -1);
192 struct Buffer *value_buf = buf_pool_get();
193 buf_strcpy_n(value_buf, value, val_size);
194 if (CONFIG_TYPE(he->type) == DT_PATH)
195 expand_path(value_buf, false);
196
197 int rv = cs_subset_he_string_set(NeoMutt->sub, he, buf_string(value_buf), err);
198 buf_pool_release(&value_buf);
199 if (CSR_RESULT(rv) != CSR_SUCCESS)
200 rc = -1;
201 break;
202 }
203 case DT_LONG:
204 case DT_NUMBER:
205 case DT_QUAD:
206 {
207 const intptr_t value = lua_tointeger(l, -1);
208 int rv = cs_subset_he_native_set(NeoMutt->sub, he, value, err);
209 if (CSR_RESULT(rv) != CSR_SUCCESS)
210 rc = -1;
211 break;
212 }
213 case DT_BOOL:
214 {
215 const intptr_t value = lua_toboolean(l, -1);
216 int rv = cs_subset_he_native_set(NeoMutt->sub, he, value, err);
217 if (CSR_RESULT(rv) != CSR_SUCCESS)
218 rc = -1;
219 break;
220 }
221 default:
222 luaL_error(l, "Unsupported NeoMutt parameter type %d for %s",
223 CONFIG_TYPE(cdef->type), param);
224 rc = -1;
225 break;
226 }
227
228 buf_pool_release(&err);
229 return rc;
230}
231
238static int lua_cb_global_get(lua_State *l)
239{
240 const char *param = lua_tostring(l, -1);
241 mutt_debug(LL_DEBUG2, "%s\n", param);
242
243 struct HashElem *he = cs_subset_lookup(NeoMutt->sub, param);
244 if (!he)
245 {
246 mutt_debug(LL_DEBUG2, "error\n");
247 luaL_error(l, "NeoMutt parameter not found %s", param);
248 return -1;
249 }
250
251 struct ConfigDef *cdef = he->data;
252
253 switch (CONFIG_TYPE(cdef->type))
254 {
255 case DT_ADDRESS:
256 case DT_ENUM:
257 case DT_EXPANDO:
258 case DT_MBTABLE:
259 case DT_MYVAR:
260 case DT_PATH:
261 case DT_REGEX:
262 case DT_SLIST:
263 case DT_SORT:
264 case DT_STRING:
265 {
266 struct Buffer *value = buf_pool_get();
267 int rc = cs_subset_he_string_get(NeoMutt->sub, he, value);
268 if (CSR_RESULT(rc) != CSR_SUCCESS)
269 {
270 buf_pool_release(&value);
271 return -1;
272 }
273
274 struct Buffer *escaped = buf_pool_get();
275 escape_string(escaped, buf_string(value));
276 lua_pushstring(l, buf_string(escaped));
277 buf_pool_release(&value);
278 buf_pool_release(&escaped);
279 return 1;
280 }
281 case DT_QUAD:
282 lua_pushinteger(l, (unsigned char) cdef->var);
283 return 1;
284 case DT_LONG:
285 lua_pushinteger(l, (signed long) cdef->var);
286 return 1;
287 case DT_NUMBER:
288 lua_pushinteger(l, (signed short) cdef->var);
289 return 1;
290 case DT_BOOL:
291 lua_pushboolean(l, (bool) cdef->var);
292 return 1;
293 default:
294 luaL_error(l, "NeoMutt parameter type %d unknown for %s", cdef->type, param);
295 return -1;
296 }
297}
298
305static int lua_cb_global_enter(lua_State *l)
306{
307 mutt_debug(LL_DEBUG2, "enter\n");
308 struct Buffer *line = buf_pool_get();
309 struct ParseContext *pc = parse_context_new();
310 struct ParseError *pe = parse_error_new();
311
312 buf_strcpy(line, lua_tostring(l, -1));
313 int rc = 0;
314
315 if (parse_rc_line(line, pc, pe))
316 {
317 luaL_error(l, "NeoMutt error: %s", buf_string(pe->message));
318 rc = -1;
319 }
320 else
321 {
322 if (!lua_pushstring(l, buf_string(pe->message)))
324 else
325 rc++;
326 }
327
328 buf_pool_release(&line);
330 parse_error_free(&pe);
331
332 return rc;
333}
334
340static int lua_cb_global_message(lua_State *l)
341{
342 mutt_debug(LL_DEBUG2, "enter\n");
343 const char *msg = lua_tostring(l, -1);
344 if (msg)
345 mutt_message("%s", msg);
346 return 0;
347}
348
354static int lua_cb_global_error(lua_State *l)
355{
356 mutt_debug(LL_DEBUG2, "enter\n");
357 const char *msg = lua_tostring(l, -1);
358 if (msg)
359 mutt_error("%s", msg);
360 return 0;
361}
362
368static void lua_expose_command(lua_State *l, const struct Command *cmd)
369{
370 char buf[1024] = { 0 };
371 snprintf(buf, sizeof(buf), "mutt.command.%s = function (...); mutt.call('%s', ...); end",
372 cmd->name, cmd->name);
373 (void) luaL_dostring(l, buf);
374}
375
385static const luaL_Reg LuaMuttCommands[] = {
386 // clang-format off
387 { "set", lua_cb_global_set },
388 { "get", lua_cb_global_get },
389 { "call", lua_cb_global_call },
390 { "enter", lua_cb_global_enter },
391 { "print", lua_cb_global_message },
392 { "message", lua_cb_global_message },
393 { "error", lua_cb_global_error },
394 { NULL, NULL },
395 // clang-format on
396};
397
403static int lua_expose_commands(lua_State *l)
404{
405 mutt_debug(LL_DEBUG2, "enter\n");
406 luaL_newlib(l, LuaMuttCommands);
407 int lib_idx = lua_gettop(l);
408
409 // clang-format off
410 lua_pushstring(l, "VERSION"); lua_pushstring(l, mutt_make_version()); lua_settable(l, lib_idx);
411 lua_pushstring(l, "QUAD_YES"); lua_pushinteger(l, MUTT_YES); lua_settable(l, lib_idx);
412 lua_pushstring(l, "QUAD_NO"); lua_pushinteger(l, MUTT_NO); lua_settable(l, lib_idx);
413 lua_pushstring(l, "QUAD_ASKYES"); lua_pushinteger(l, MUTT_ASKYES); lua_settable(l, lib_idx);
414 lua_pushstring(l, "QUAD_ASKNO"); lua_pushinteger(l, MUTT_ASKNO); lua_settable(l, lib_idx);
415 // clang-format on
416
417 return 1;
418}
419
424static void lua_expose_mutt(lua_State *l)
425{
426 luaL_requiref(l, "mutt", lua_expose_commands, 1);
427 (void) luaL_dostring(l, "mutt.command = {}");
428
429 const struct Command **cp = NULL;
431 {
432 const struct Command *cmd = *cp;
433
434 lua_expose_command(l, cmd);
435 }
436}
437
443bool lua_init_state(lua_State **l)
444{
445 if (!l)
446 return false;
447 if (*l)
448 return true;
449
450 mutt_debug(LL_DEBUG2, "enter\n");
451 *l = luaL_newstate();
452
453 if (!*l)
454 {
455 mutt_error(_("Error: Couldn't load the lua interpreter"));
456 return false;
457 }
458
459 lua_atpanic(*l, lua_handle_panic);
460
461 /* load various Lua libraries */
462 luaL_openlibs(*l);
463 lua_expose_mutt(*l);
464
465 return true;
466}
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition array.h:223
void buf_seek(struct Buffer *buf, size_t offset)
Set current read/write position to offset from beginning.
Definition buffer.c:622
size_t buf_strcpy_n(struct Buffer *buf, const char *s, size_t len)
Copy a string into a Buffer.
Definition buffer.c:416
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition buffer.c:241
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition buffer.c:226
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition buffer.c:395
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
size_t escape_string(struct Buffer *buf, const char *src)
Write a string to a buffer, escaping special characters.
Definition dump.c:47
Convenience wrapper for the config headers.
struct HashElem * cs_create_variable(const struct ConfigSet *cs, struct ConfigDef *cdef, struct Buffer *err)
Create and register one config item.
Definition set.c:327
#define CSR_RESULT(x)
Extract the result code from CSR_* flags.
Definition set.h:53
#define CSR_SUCCESS
Action completed successfully.
Definition set.h:33
const struct Command * commands_get(struct CommandArray *ca, const char *name)
Get a Command by its name.
Definition command.c:82
Convenience wrapper for the core headers.
#define mutt_error(...)
Definition logging2.h:94
#define mutt_message(...)
Definition logging2.h:93
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
@ LL_DEBUG2
Log at debug level 2.
Definition logging2.h:46
@ LL_DEBUG1
Log at debug level 1.
Definition logging2.h:45
bool lua_init_state(lua_State **l)
Initialise a Lua State.
Definition lua.c:443
static int lua_expose_commands(lua_State *l)
Declare some NeoMutt types to the Lua interpreter.
Definition lua.c:403
static int lua_cb_global_set(lua_State *l)
Set a NeoMutt variable.
Definition lua.c:147
static void lua_expose_mutt(lua_State *l)
Expose a 'Mutt' object to the Lua interpreter.
Definition lua.c:424
static void lua_expose_command(lua_State *l, const struct Command *cmd)
Expose a NeoMutt command to the Lua interpreter.
Definition lua.c:368
static int lua_cb_global_message(lua_State *l)
Display a message in NeoMutt.
Definition lua.c:340
static int lua_handle_panic(lua_State *l)
Handle a panic in the Lua interpreter.
Definition lua.c:60
static int lua_cb_global_error(lua_State *l)
Display an error in NeoMutt.
Definition lua.c:354
static const luaL_Reg LuaMuttCommands[]
List of Lua commands to register.
Definition lua.c:385
static int lua_cb_global_enter(lua_State *l)
Execute NeoMutt config from Lua.
Definition lua.c:305
static int lua_cb_global_get(lua_State *l)
Get a NeoMutt variable.
Definition lua.c:238
static int lua_handle_error(lua_State *l)
Handle an error in the Lua interpreter.
Definition lua.c:73
static int lua_cb_global_call(lua_State *l)
Call a NeoMutt command by name.
Definition lua.c:87
Convenience wrapper for the library headers.
#define _(a)
Definition message.h:28
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition string.c:234
void expand_path(struct Buffer *buf, bool regex)
Create the canonical path.
Definition muttlib.c:122
Some miscellaneous functions.
Text parsing functions.
void parse_context_free(struct ParseContext **pptr)
Free a ParseContext.
Definition pcontext.c:48
struct ParseContext * parse_context_new(void)
Create a new ParseContext.
Definition pcontext.c:37
void parse_error_free(struct ParseError **pptr)
Free a ParseError.
Definition perror.c:50
struct ParseError * parse_error_new(void)
Create a new ParseError.
Definition perror.c:37
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:91
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:111
@ MUTT_ASKNO
Ask the user, defaulting to 'No'.
Definition quad.h:40
@ MUTT_NO
User answered 'No', or assume 'No'.
Definition quad.h:38
@ MUTT_ASKYES
Ask the user, defaulting to 'Yes'.
Definition quad.h:41
@ MUTT_YES
User answered 'Yes', or assume 'Yes'.
Definition quad.h:39
enum CommandResult parse_rc_line(struct Buffer *line, struct ParseContext *pc, struct ParseError *pe)
Parse a line of user config.
Definition rc.c:45
String manipulation buffer.
Definition buffer.h:36
enum CommandResult(* parse)(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe)
Definition command.h:181
const char * name
Name of the Command.
Definition command.h:162
const char * name
User-visible name.
Definition set.h:66
intptr_t var
Storage for the variable.
Definition set.h:85
uint32_t type
Variable type, e.g. DT_STRING.
Definition set.h:67
struct ConfigSet * cs
Parent ConfigSet.
Definition subset.h:50
The item stored in a Hash Table.
Definition hash.h:44
int type
Type of data stored in Hash Table, e.g. DT_STRING.
Definition hash.h:45
void * data
User-supplied data.
Definition hash.h:47
Container for Accounts, Notifications.
Definition neomutt.h:41
struct CommandArray commands
NeoMutt commands.
Definition neomutt.h:53
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49
Context for config parsing (history/backtrace)
Definition pcontext.h:34
Detailed error information from config parsing.
Definition perror.h:34
struct Buffer * message
Error message.
Definition perror.h:35
int cs_subset_he_string_get(const struct ConfigSubset *sub, struct HashElem *he, struct Buffer *result)
Get a config item as a string.
Definition subset.c:338
int cs_subset_he_native_set(const struct ConfigSubset *sub, struct HashElem *he, intptr_t value, struct Buffer *err)
Natively set the value of a HashElem config item.
Definition subset.c:281
int cs_subset_he_string_set(const struct ConfigSubset *sub, struct HashElem *he, const char *value, struct Buffer *err)
Set a config item by string.
Definition subset.c:370
struct HashElem * cs_subset_lookup(const struct ConfigSubset *sub, const char *name)
Find an inherited config item.
Definition subset.c:193
#define CONFIG_TYPE(t)
Extract the type from the flags.
Definition types.h:50
@ DT_NUMBER
a number
Definition types.h:38
@ DT_SLIST
a list of strings
Definition types.h:42
@ DT_BOOL
boolean option
Definition types.h:32
@ DT_QUAD
quad-option (no/yes/ask-no/ask-yes)
Definition types.h:40
@ DT_STRING
a string
Definition types.h:44
@ DT_SORT
sorting methods
Definition types.h:43
@ DT_MYVAR
a user-defined variable (my_foo)
Definition types.h:37
@ DT_MBTABLE
multibyte char table
Definition types.h:36
@ DT_ADDRESS
e-mail address
Definition types.h:31
@ DT_LONG
a number (long)
Definition types.h:35
@ DT_EXPANDO
an expando
Definition types.h:34
@ DT_ENUM
an enumeration
Definition types.h:33
@ DT_REGEX
regular expressions
Definition types.h:41
@ DT_PATH
a path to a file/directory
Definition types.h:39
const char * mutt_make_version(void)
Generate the NeoMutt version string.
Definition version.c:293
Display version and copyright about NeoMutt.