about summary refs log tree commit homepage
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/wordexp/extconf.rb5
-rw-r--r--ext/wordexp/wordexp.c176
2 files changed, 181 insertions, 0 deletions
diff --git a/ext/wordexp/extconf.rb b/ext/wordexp/extconf.rb
new file mode 100644
index 0000000..aa7f4b6
--- /dev/null
+++ b/ext/wordexp/extconf.rb
@@ -0,0 +1,5 @@
+require 'mkmf'
+have_func('wordexp', %w(wordexp.h))
+have_func('wordfree', %w(wordexp.h))
+have_func('rb_thread_call_without_gvl')
+create_makefile('wordexp')
diff --git a/ext/wordexp/wordexp.c b/ext/wordexp/wordexp.c
new file mode 100644
index 0000000..b50ec2d
--- /dev/null
+++ b/ext/wordexp/wordexp.c
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
+ * License: LGPLv2.1 or later (see COPYING for details)
+ */
+#include <ruby.h>
+#include <wordexp.h>
+static VALUE cErr;
+static VALUE cBadchar;
+static VALUE cBadval;
+static VALUE cCmdsub;
+static VALUE cSyntax;
+
+struct wordexp_args {
+        const char *s;
+        wordexp_t p;
+        int flags;
+        int err;
+};
+
+static void *nogvl_wordexp(void *arg)
+{
+        struct wordexp_args *a = arg;
+
+        a->err = wordexp(a->s, &a->p, a->flags);
+
+        return NULL;
+}
+
+#if defined(HAVE_RUBY_THREAD_H) && HAVE_RUBY_THREAD_H && \
+   defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
+#include <ruby/thread.h>
+#define nogvl(fn,arg) rb_thread_call_without_gvl(fn, arg, 0, NULL)
+#else
+#  define COMPAT_FN (VALUE (*)(void *))
+#  define nogvl(fn,arg) rb_thread_blocking_region(COMPAT_FN(fn), arg, 0, NULL)
+#endif
+
+/*
+ * wordexp performs word expansion like a POSIX-shell
+ *
+ * call-seq:
+ *     wordexp("hello $(echo world)")                -> [ "hello", "world" ]
+ *     wordexp("untrusted string", WRDE_NOCMD)        -> [ "untrusted", "string" ]
+ */
+static VALUE my_wordexp(int argc, VALUE *argv, VALUE self)
+{
+        VALUE rstr;
+        VALUE rflags = Qnil;
+        int retryable = 1;
+        struct wordexp_args a;
+
+        rb_scan_args(argc, argv, "11", &rstr, &rflags);
+
+        a.s = StringValuePtr(rstr);
+        a.flags = NIL_P(rflags) ? 0 : NUM2INT(rflags);
+
+retry:
+        if (a.flags & WRDE_NOCMD)
+                a.err = wordexp(a.s, &a.p, a.flags);
+        else
+                /* release GVL if command substition is used */
+                nogvl(nogvl_wordexp, &a);
+
+        if (a.err == 0) { /* success, map results to a Ruby array */
+                VALUE ary = rb_ary_new2(a.p.we_wordc);
+                char **w = a.p.we_wordv;
+                size_t i;
+
+                for (i = 0; i < a.p.we_wordc; i++)
+                        rb_ary_store(ary, i, rb_str_new2(w[i]));
+
+                wordfree(&a.p);
+
+                return ary;
+        }
+
+        switch (a.err) {
+        case WRDE_BADCHAR:
+                rb_raise(cBadchar,
+                         "Illegal newline or one of |, &, ;, <, >, (, ), {, }");
+        case WRDE_BADVAL:
+                rb_raise(cBadval,
+                         "undefined shell variable (with WRDE_UNDEF)");
+        case WRDE_CMDSUB:
+                rb_raise(cCmdsub,
+                         "command substition not allowed (WRDE_NOCMD used)");
+        case WRDE_NOSPACE:
+                rb_gc();
+                if (!retryable)
+                        rb_memerror();
+                retryable = 0;
+                goto retry;
+        case WRDE_SYNTAX:
+                rb_raise(cSyntax, "shell syntax error");
+        }
+
+        rb_raise(cErr, "wordexp returned unknown error: %d", a.err);
+}
+
+void Init_wordexp(void)
+{
+        /*
+         * Document-module: Wordexp
+         *
+         * The Wordexp module only includes the wordexp method and
+         * WRDE_* constants.  Include this module into any class to
+         * use the wordexp method.
+         *
+         * call-seq:
+         *        include Wordexp
+         */
+        VALUE mod = rb_define_module("Wordexp");
+        cErr = rb_define_class_under(mod, "WordexpError", rb_eArgError);
+
+        rb_define_method(mod, "wordexp", my_wordexp, -1);
+
+        /*
+         * Array manipulation is easy and implicit in Ruby, so
+         * we probably do not need these.
+         *
+         * WRDE_APPEND
+         * WRDE_DOOFFS
+         * WRDE_REUSE
+         *
+         * Please contact us at ruby.wordexp@librelist.org if
+         * you can make a case for supporting these.
+         */
+
+        /*
+         * Document-constant: WRDE_NOCMD
+         * Do not do command substition.  Command-substition is enabled
+         * by default, so it is possible to read the standard output of
+         * arbitrary commands.
+         */
+        rb_define_const(mod, "WRDE_NOCMD", INT2FIX(WRDE_NOCMD));
+
+        /*
+         * Document-constant: WRDE_SHOWERR
+         * Do not redirect stderr to /dev/null when performing
+         * command substition.  Normally, stderr is directed to /dev/null.
+         */
+        rb_define_const(mod, "WRDE_SHOWERR", INT2FIX(WRDE_SHOWERR));
+
+        /*
+         * Document-constant: WRDE_UNDEF
+         * Causes WordexpError::Badval to be raised if an undefined
+         * environment variable is used.  Otherwise undefined variables
+         * will evaluate to an empty string
+         */
+        rb_define_const(mod, "WRDE_UNDEF", INT2FIX(WRDE_UNDEF));
+
+        /*
+         * An illegal character appeared in the argument.
+         * One of: newline, |, &, ;, <, >, (, ), {, or }
+         */
+        cBadchar = rb_define_class_under(cErr, "Badchar", cErr);
+
+        /*
+         * An undefined environment variable was referenced and WRDE_UNDEF
+         * was used
+         */
+        cBadval = rb_define_class_under(cErr, "Badval", cErr);
+
+        /*
+         * WRDE_NOCMD was specified and command substitution appeared
+         */
+        cCmdsub = rb_define_class_under(cErr, "Cmdsub", cErr);
+
+        /* noexception for WRDE_NOSPACE, rb_memerror instead */
+
+        /*
+         * A shell sintax error occurred, such as unbalanced quotes
+         * or parentheses.
+         */
+        cSyntax = rb_define_class_under(cErr, "Syntax", cErr);
+}