2005-09-15 09:58:31 -04:00
|
|
|
/* Ruby interface (scripting engine) */
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
|
|
|
|
2022-01-15 11:56:00 -05:00
|
|
|
#undef _GNU_SOURCE
|
2005-09-15 09:58:31 -04:00
|
|
|
#include <ruby.h>
|
2022-01-07 12:59:22 -05:00
|
|
|
#include <ruby/version.h>
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
#undef _
|
|
|
|
|
|
|
|
#include "elinks.h"
|
|
|
|
|
|
|
|
#include "bfu/dialog.h"
|
|
|
|
#include "config/home.h"
|
2021-08-08 15:25:08 -04:00
|
|
|
#include "intl/libintl.h"
|
2005-09-15 09:58:31 -04:00
|
|
|
#include "main/module.h"
|
2006-07-02 02:44:52 -04:00
|
|
|
#include "osdep/osdep.h"
|
2005-10-20 22:07:43 -04:00
|
|
|
#include "scripting/scripting.h"
|
2005-09-15 09:58:31 -04:00
|
|
|
#include "scripting/ruby/core.h"
|
2005-10-20 22:07:43 -04:00
|
|
|
#include "scripting/ruby/ruby.h"
|
2005-09-15 09:58:31 -04:00
|
|
|
#include "util/error.h"
|
|
|
|
#include "util/file.h"
|
|
|
|
#include "util/string.h"
|
|
|
|
|
|
|
|
#define RUBY_HOOKS_FILENAME "hooks.rb"
|
|
|
|
|
|
|
|
|
|
|
|
/* I've decided to use `erb' to refer to this ELinks/ruby thingy. */
|
|
|
|
|
|
|
|
VALUE erb_module;
|
|
|
|
|
|
|
|
|
|
|
|
/* Error reporting. */
|
|
|
|
|
|
|
|
void
|
2022-01-30 13:27:23 -05:00
|
|
|
alert_ruby_error(struct session *ses, const char *msg)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
2005-10-20 22:07:43 -04:00
|
|
|
report_scripting_error(&ruby_scripting_module, ses, msg);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Another Vim treat. */
|
|
|
|
void
|
|
|
|
erb_report_error(struct session *ses, int error)
|
|
|
|
{
|
|
|
|
VALUE eclass;
|
|
|
|
VALUE einfo;
|
2021-01-02 10:20:27 -05:00
|
|
|
char buff[MAX_STR_LEN];
|
|
|
|
char *msg;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
/* XXX: Ew. These are from the Ruby internals. */
|
|
|
|
#define TAG_RETURN 0x1
|
|
|
|
#define TAG_BREAK 0x2
|
|
|
|
#define TAG_NEXT 0x3
|
|
|
|
#define TAG_RETRY 0x4
|
|
|
|
#define TAG_REDO 0x5
|
|
|
|
#define TAG_RAISE 0x6
|
|
|
|
#define TAG_THROW 0x7
|
|
|
|
#define TAG_FATAL 0x8
|
|
|
|
#define TAG_MASK 0xf
|
|
|
|
|
|
|
|
switch (error) {
|
|
|
|
case TAG_RETURN:
|
|
|
|
msg = "unexpected return";
|
|
|
|
break;
|
|
|
|
case TAG_NEXT:
|
|
|
|
msg = "unexpected next";
|
|
|
|
break;
|
|
|
|
case TAG_BREAK:
|
|
|
|
msg = "unexpected break";
|
|
|
|
break;
|
|
|
|
case TAG_REDO:
|
|
|
|
msg = "unexpected redo";
|
|
|
|
break;
|
|
|
|
case TAG_RETRY:
|
|
|
|
msg = "retry outside of rescue clause";
|
|
|
|
break;
|
|
|
|
case TAG_RAISE:
|
|
|
|
case TAG_FATAL:
|
2018-03-09 17:31:40 -05:00
|
|
|
eclass = CLASS_OF(RB_ERRINFO);
|
|
|
|
einfo = rb_obj_as_string(RB_ERRINFO);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2018-03-09 17:31:40 -05:00
|
|
|
if (eclass == rb_eRuntimeError && RSTRING_LEN(einfo) == 0) {
|
2005-09-15 09:58:31 -04:00
|
|
|
msg = "unhandled exception";
|
|
|
|
|
|
|
|
} else {
|
|
|
|
VALUE epath;
|
2021-01-02 10:20:27 -05:00
|
|
|
char *p;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
epath = rb_class_path(eclass);
|
|
|
|
snprintf(buff, MAX_STR_LEN, "%s: %s",
|
2018-03-09 17:31:40 -05:00
|
|
|
RSTRING_PTR(epath), RSTRING_PTR(einfo));
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2022-01-18 14:30:48 -05:00
|
|
|
p = strchr(buff, '\n');
|
2005-09-15 09:58:31 -04:00
|
|
|
if (p) *p = '\0';
|
|
|
|
msg = buff;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
snprintf(buff, MAX_STR_LEN, "unknown longjmp status %d", error);
|
|
|
|
msg = buff;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
alert_ruby_error(ses, msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* The ELinks module: */
|
|
|
|
|
|
|
|
/* Inspired by Vim this is used to hook into the stdout write method so written
|
|
|
|
* data is displayed in a nice message box. */
|
|
|
|
static VALUE
|
|
|
|
erb_module_message(VALUE self, VALUE str)
|
|
|
|
{
|
2021-01-02 10:20:27 -05:00
|
|
|
char *message, *line_end;
|
bug 1054: Don't abort downloads when closing a terminal.
Except if they have external handlers.
When ELinks receives an event from a terminal, move that terminal to
the beginning of the global "terminals" list, so that the terminals
are always sorted according to the time of the most recent use. Note,
this affects the numbering of bookmark folders in session snapshots.
Add get_default_terminal(), which returns the most recently used
terminal that is still open. Use that in various places that
previously used terminals.prev or terminals.next. Four functions
fetch the size of the terminal for User-Agent headers, and
get_default_terminal() is not really right, but neither was the
original code; add TODO comments in those functions.
When the user chooses "Background and Notify", associate the download
with the terminal where the dialog box is. So any later messages will
then appear in that terminal, if it is still open. However, don't
change the terminal if the download has an external handler.
When a download gets some data, don't immediately check the associated
terminal. Instead, wait for the download to end. Then, if the
terminal of the download has been closed, use get_default_terminal()
instead. If there is no default terminal either, just skip any
message boxes.
2008-10-15 04:05:43 -04:00
|
|
|
struct terminal *term;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
str = rb_obj_as_string(str);
|
2018-03-10 04:27:46 -05:00
|
|
|
message = memacpy(RSTRING_PTR(str), RSTRING_LEN(str));
|
2005-09-15 09:58:31 -04:00
|
|
|
if (!message) return Qnil;
|
|
|
|
|
2022-01-18 14:30:48 -05:00
|
|
|
line_end = strchr(message, '\n');
|
2005-09-15 09:58:31 -04:00
|
|
|
if (line_end) *line_end = '\0';
|
|
|
|
|
bug 1054: Don't abort downloads when closing a terminal.
Except if they have external handlers.
When ELinks receives an event from a terminal, move that terminal to
the beginning of the global "terminals" list, so that the terminals
are always sorted according to the time of the most recent use. Note,
this affects the numbering of bookmark folders in session snapshots.
Add get_default_terminal(), which returns the most recently used
terminal that is still open. Use that in various places that
previously used terminals.prev or terminals.next. Four functions
fetch the size of the terminal for User-Agent headers, and
get_default_terminal() is not really right, but neither was the
original code; add TODO comments in those functions.
When the user chooses "Background and Notify", associate the download
with the terminal where the dialog box is. So any later messages will
then appear in that terminal, if it is still open. However, don't
change the terminal if the download has an external handler.
When a download gets some data, don't immediately check the associated
terminal. Instead, wait for the download to end. Then, if the
terminal of the download has been closed, use get_default_terminal()
instead. If there is no default terminal either, just skip any
message boxes.
2008-10-15 04:05:43 -04:00
|
|
|
term = get_default_terminal();
|
|
|
|
if (!term) {
|
2005-09-15 09:58:31 -04:00
|
|
|
usrerror("[Ruby] %s", message);
|
|
|
|
mem_free(message);
|
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
|
bug 1054: Don't abort downloads when closing a terminal.
Except if they have external handlers.
When ELinks receives an event from a terminal, move that terminal to
the beginning of the global "terminals" list, so that the terminals
are always sorted according to the time of the most recent use. Note,
this affects the numbering of bookmark folders in session snapshots.
Add get_default_terminal(), which returns the most recently used
terminal that is still open. Use that in various places that
previously used terminals.prev or terminals.next. Four functions
fetch the size of the terminal for User-Agent headers, and
get_default_terminal() is not really right, but neither was the
original code; add TODO comments in those functions.
When the user chooses "Background and Notify", associate the download
with the terminal where the dialog box is. So any later messages will
then appear in that terminal, if it is still open. However, don't
change the terminal if the download has an external handler.
When a download gets some data, don't immediately check the associated
terminal. Instead, wait for the download to end. Then, if the
terminal of the download has been closed, use get_default_terminal()
instead. If there is no default terminal either, just skip any
message boxes.
2008-10-15 04:05:43 -04:00
|
|
|
info_box(term, MSGBOX_NO_TEXT_INTL | MSGBOX_FREE_TEXT,
|
2005-09-15 09:58:31 -04:00
|
|
|
N_("Ruby Message"), ALIGN_LEFT, message);
|
|
|
|
|
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The global Kernel::p method will for each object, directly write
|
|
|
|
* object.inspect() followed by the current output record separator to the
|
|
|
|
* program's standard output and will bypass the Ruby I/O libraries.
|
|
|
|
*
|
|
|
|
* Inspired by Vim we hook into the method and pop up a nice message box so it
|
|
|
|
* can be used to easily debug scripts without dirtying the screen. */
|
|
|
|
static VALUE
|
|
|
|
erb_stdout_p(int argc, VALUE *argv, VALUE self)
|
|
|
|
{
|
|
|
|
int i;
|
2019-04-21 06:27:40 -04:00
|
|
|
struct string string;
|
bug 1054: Don't abort downloads when closing a terminal.
Except if they have external handlers.
When ELinks receives an event from a terminal, move that terminal to
the beginning of the global "terminals" list, so that the terminals
are always sorted according to the time of the most recent use. Note,
this affects the numbering of bookmark folders in session snapshots.
Add get_default_terminal(), which returns the most recently used
terminal that is still open. Use that in various places that
previously used terminals.prev or terminals.next. Four functions
fetch the size of the terminal for User-Agent headers, and
get_default_terminal() is not really right, but neither was the
original code; add TODO comments in those functions.
When the user chooses "Background and Notify", associate the download
with the terminal where the dialog box is. So any later messages will
then appear in that terminal, if it is still open. However, don't
change the terminal if the download has an external handler.
When a download gets some data, don't immediately check the associated
terminal. Instead, wait for the download to end. Then, if the
terminal of the download has been closed, use get_default_terminal()
instead. If there is no default terminal either, just skip any
message boxes.
2008-10-15 04:05:43 -04:00
|
|
|
struct terminal *term;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
if (!init_string(&string))
|
|
|
|
return Qnil;
|
|
|
|
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
|
|
VALUE substr;
|
2021-01-02 10:20:27 -05:00
|
|
|
char *ptr;
|
2005-09-15 09:58:31 -04:00
|
|
|
int len;
|
|
|
|
|
|
|
|
if (i > 0)
|
|
|
|
add_to_string(&string, ", ");
|
|
|
|
|
|
|
|
substr = rb_inspect(argv[i]);
|
|
|
|
|
|
|
|
/* The Ruby p() function writes variable number of objects using
|
|
|
|
* the inspect() method, which adds quotes to the strings, so
|
|
|
|
* gently ignore them. */
|
|
|
|
|
2018-03-09 17:31:40 -05:00
|
|
|
ptr = RSTRING_PTR(substr);
|
|
|
|
len = RSTRING_LEN(substr);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
if (*ptr == '"')
|
|
|
|
ptr++, len--;
|
|
|
|
|
|
|
|
if (ptr[len - 1] == '"')
|
|
|
|
len--;
|
|
|
|
|
|
|
|
add_bytes_to_string(&string, ptr, len);
|
|
|
|
}
|
|
|
|
|
bug 1054: Don't abort downloads when closing a terminal.
Except if they have external handlers.
When ELinks receives an event from a terminal, move that terminal to
the beginning of the global "terminals" list, so that the terminals
are always sorted according to the time of the most recent use. Note,
this affects the numbering of bookmark folders in session snapshots.
Add get_default_terminal(), which returns the most recently used
terminal that is still open. Use that in various places that
previously used terminals.prev or terminals.next. Four functions
fetch the size of the terminal for User-Agent headers, and
get_default_terminal() is not really right, but neither was the
original code; add TODO comments in those functions.
When the user chooses "Background and Notify", associate the download
with the terminal where the dialog box is. So any later messages will
then appear in that terminal, if it is still open. However, don't
change the terminal if the download has an external handler.
When a download gets some data, don't immediately check the associated
terminal. Instead, wait for the download to end. Then, if the
terminal of the download has been closed, use get_default_terminal()
instead. If there is no default terminal either, just skip any
message boxes.
2008-10-15 04:05:43 -04:00
|
|
|
term = get_default_terminal();
|
|
|
|
if (!term) {
|
2005-09-15 09:58:31 -04:00
|
|
|
usrerror("[Ruby] %s", string.source);
|
|
|
|
done_string(&string);
|
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
|
bug 1054: Don't abort downloads when closing a terminal.
Except if they have external handlers.
When ELinks receives an event from a terminal, move that terminal to
the beginning of the global "terminals" list, so that the terminals
are always sorted according to the time of the most recent use. Note,
this affects the numbering of bookmark folders in session snapshots.
Add get_default_terminal(), which returns the most recently used
terminal that is still open. Use that in various places that
previously used terminals.prev or terminals.next. Four functions
fetch the size of the terminal for User-Agent headers, and
get_default_terminal() is not really right, but neither was the
original code; add TODO comments in those functions.
When the user chooses "Background and Notify", associate the download
with the terminal where the dialog box is. So any later messages will
then appear in that terminal, if it is still open. However, don't
change the terminal if the download has an external handler.
When a download gets some data, don't immediately check the associated
terminal. Instead, wait for the download to end. Then, if the
terminal of the download has been closed, use get_default_terminal()
instead. If there is no default terminal either, just skip any
message boxes.
2008-10-15 04:05:43 -04:00
|
|
|
info_box(term, MSGBOX_NO_TEXT_INTL | MSGBOX_FREE_TEXT,
|
2005-09-15 09:58:31 -04:00
|
|
|
N_("Ruby Message"), ALIGN_LEFT, string.source);
|
|
|
|
|
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ELinks::method_missing() is a catch all method that will be called when a
|
|
|
|
* hook is not defined. It might not be so elegant but it removes NoMethodErrors
|
|
|
|
* from popping up. */
|
|
|
|
/* FIXME: It might be useful for user to actually display them to debug scripts,
|
|
|
|
* so maybe it should be optional. --jonas */
|
|
|
|
static VALUE
|
2021-12-12 09:07:49 -05:00
|
|
|
erb_module_method_missing(int argc, VALUE *argv, VALUE self)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
init_erb_module(void)
|
|
|
|
{
|
2021-01-02 10:20:27 -05:00
|
|
|
char *home;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
erb_module = rb_define_module("ELinks");
|
|
|
|
rb_define_const(erb_module, "VERSION", rb_str_new2(VERSION_STRING));
|
|
|
|
|
2021-01-02 10:20:27 -05:00
|
|
|
home = elinks_home ? elinks_home : (char *) CONFDIR;
|
2005-09-15 09:58:31 -04:00
|
|
|
rb_define_const(erb_module, "HOME", rb_str_new2(home));
|
|
|
|
|
|
|
|
rb_define_module_function(erb_module, "message", erb_module_message, 1);
|
|
|
|
rb_define_module_function(erb_module, "method_missing", erb_module_method_missing, -1);
|
|
|
|
rb_define_module_function(erb_module, "p", erb_stdout_p, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-01-07 12:59:22 -05:00
|
|
|
static char elrubyversion[32];
|
|
|
|
|
2005-09-15 09:58:31 -04:00
|
|
|
void
|
|
|
|
init_ruby(struct module *module)
|
|
|
|
{
|
2021-01-02 10:20:27 -05:00
|
|
|
char *path;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
/* Set up and initialize the interpreter. This function should be called
|
|
|
|
* before any other Ruby-related functions. */
|
|
|
|
ruby_init();
|
|
|
|
ruby_script("ELinks-ruby");
|
|
|
|
ruby_init_loadpath();
|
|
|
|
|
|
|
|
/* ``Trap'' debug prints from scripts. */
|
|
|
|
rb_define_singleton_method(rb_stdout, "write", erb_module_message, 1);
|
|
|
|
rb_define_global_function("p", erb_stdout_p, -1);
|
|
|
|
|
|
|
|
/* Set up the ELinks module interface. */
|
|
|
|
init_erb_module();
|
|
|
|
|
2022-01-07 12:59:22 -05:00
|
|
|
snprintf(elrubyversion, 31, "Ruby %s", ruby_version);
|
|
|
|
module->name = elrubyversion;
|
|
|
|
|
2005-09-15 09:58:31 -04:00
|
|
|
if (elinks_home) {
|
2007-03-11 06:59:11 -04:00
|
|
|
path = straconcat(elinks_home, RUBY_HOOKS_FILENAME,
|
2021-01-02 10:20:27 -05:00
|
|
|
(char *) NULL);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
} else {
|
2006-07-02 02:44:52 -04:00
|
|
|
path = stracpy(CONFDIR STRING_DIR_SEP RUBY_HOOKS_FILENAME);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!path) return;
|
|
|
|
|
|
|
|
if (file_can_read(path)) {
|
2021-08-17 10:09:05 -04:00
|
|
|
int error = 0;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
/* Load ~/.elinks/hooks.rb into the interpreter. */
|
|
|
|
//rb_load_file(path);
|
|
|
|
rb_load_protect(rb_str_new2(path), 0, &error);
|
|
|
|
if (error)
|
|
|
|
erb_report_error(NULL, error);
|
|
|
|
}
|
|
|
|
|
|
|
|
mem_free(path);
|
|
|
|
}
|