[前][次][番号順一覧][スレッド一覧]

ruby-changes:50730

From: naruse <ko1@a...>
Date: Sat, 24 Mar 2018 19:57:40 +0900 (JST)
Subject: [ruby-changes:50730] naruse:r62905 (ruby_2_5): merge revision(s) 62548, 62894: [Backport #14324]

naruse	2018-03-24 19:57:34 +0900 (Sat, 24 Mar 2018)

  New Revision: 62905

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=62905

  Log:
    merge revision(s) 62548,62894: [Backport #14324]
    
    eval_error.c: rb_error_write flags
    
    * eval_error.c (rb_error_write): add highlight and reverse mode
      flags.  defaulted to rb_stderr_tty_p() if Qnil.
    
    error.c: full_message options
    
    * error.c (exc_full_message): add highlight: and reverse: keyword
      options.  [Bug #14324]

  Modified directories:
    branches/ruby_2_5/
  Modified files:
    branches/ruby_2_5/NEWS
    branches/ruby_2_5/error.c
    branches/ruby_2_5/eval_error.c
    branches/ruby_2_5/test/ruby/test_exception.rb
    branches/ruby_2_5/version.h
Index: ruby_2_5/error.c
===================================================================
--- ruby_2_5/error.c	(revision 62904)
+++ ruby_2_5/error.c	(revision 62905)
@@ -859,7 +859,7 @@ static VALUE rb_eNOERROR; https://github.com/ruby/ruby/blob/trunk/ruby_2_5/error.c#L859
 static ID id_new, id_cause, id_message, id_backtrace;
 static ID id_name, id_key, id_args, id_Errno, id_errno, id_i_path;
 static ID id_receiver, id_iseq, id_local_variables;
-static ID id_private_call_p;
+static ID id_private_call_p, id_top, id_bottom;
 extern ID ruby_static_id_status;
 #define id_bt idBt
 #define id_bt_locations idBt_locations
@@ -951,24 +951,79 @@ exc_to_s(VALUE exc) https://github.com/ruby/ruby/blob/trunk/ruby_2_5/error.c#L951
 }
 
 /* FIXME: Include eval_error.c */
-void rb_error_write(VALUE errinfo, VALUE errat, VALUE str);
+void rb_error_write(VALUE errinfo, VALUE errat, VALUE str, VALUE highlight, VALUE reverse);
 
 /*
  * call-seq:
- *   exception.full_message  ->  string
+ *    Exception.to_tty?   ->  true or false
  *
- * Returns formatted string of <i>exception</i>.
+ * Returns +true+ if exception messages will be sent to a tty.
+ */
+static VALUE
+exc_s_to_tty_p(VALUE self)
+{
+    return rb_stderr_tty_p() ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq:
+ *   exception.full_message(highlight: bool, order: [:top or :bottom]) ->  string
+ *
+ * Returns formatted string of _exception_.
  * The returned string is formatted using the same format that Ruby uses
- * when printing an uncaught exceptions to stderr. So it may differ by
- * <code>$stderr.tty?</code> at the timing of a call.
+ * when printing an uncaught exceptions to stderr.
+ *
+ * If _highlight_ is +true+ the default error handler will send the
+ * messages to a tty.
+ *
+ * _order_ must be either of +:top+ or +:bottom+, and places the error
+ * message and the innermost backtrace come at the top or the bottom.
+ *
+ * The default values of these options depend on <code>$stderr</code>
+ * and its +tty?+ at the timing of a call.
  */
 
 static VALUE
-exc_full_message(VALUE exc)
+exc_full_message(int argc, VALUE *argv, VALUE exc)
 {
-    VALUE str = rb_str_new2("");
-    VALUE errat = rb_get_backtrace(exc);
-    rb_error_write(exc, errat, str);
+    VALUE opt, str, errat;
+    enum {kw_highlight, kw_order, kw_max_};
+    static ID kw[kw_max_];
+    VALUE args[kw_max_] = {Qnil, Qnil};
+
+    rb_scan_args(argc, argv, "0:", &opt);
+    if (!NIL_P(opt)) {
+	if (!kw[0]) {
+#define INIT_KW(n) kw[kw_##n] = rb_intern_const(#n)
+	    INIT_KW(highlight);
+	    INIT_KW(order);
+#undef INIT_KW
+	}
+	rb_get_kwargs(opt, kw, 0, kw_max_, args);
+	switch (args[kw_highlight]) {
+	  default:
+	    rb_raise(rb_eArgError, "expected true or false as "
+		     "highlight: %+"PRIsVALUE, args[kw_highlight]);
+	  case Qundef: args[kw_highlight] = Qnil; break;
+	  case Qtrue: case Qfalse: case Qnil: break;
+	}
+	if (args[kw_order] == Qundef) {
+	    args[kw_order] = Qnil;
+	}
+	else {
+	    ID id = rb_check_id(&args[kw_order]);
+	    if (id == id_bottom) args[kw_order] = Qtrue;
+	    else if (id == id_top) args[kw_order] = Qfalse;
+	    else {
+		rb_raise(rb_eArgError, "expected :top or :down as "
+			 "order: %+"PRIsVALUE, args[kw_order]);
+	    }
+	}
+    }
+    str = rb_str_new2("");
+    errat = rb_get_backtrace(exc);
+
+    rb_error_write(exc, errat, str, args[kw_highlight], args[kw_order]);
     return str;
 }
 
@@ -2250,12 +2305,13 @@ Init_Exception(void) https://github.com/ruby/ruby/blob/trunk/ruby_2_5/error.c#L2305
 {
     rb_eException   = rb_define_class("Exception", rb_cObject);
     rb_define_singleton_method(rb_eException, "exception", rb_class_new_instance, -1);
+    rb_define_singleton_method(rb_eException, "to_tty?", exc_s_to_tty_p, 0);
     rb_define_method(rb_eException, "exception", exc_exception, -1);
     rb_define_method(rb_eException, "initialize", exc_initialize, -1);
     rb_define_method(rb_eException, "==", exc_equal, 1);
     rb_define_method(rb_eException, "to_s", exc_to_s, 0);
     rb_define_method(rb_eException, "message", exc_message, 0);
-    rb_define_method(rb_eException, "full_message", exc_full_message, 0);
+    rb_define_method(rb_eException, "full_message", exc_full_message, -1);
     rb_define_method(rb_eException, "inspect", exc_inspect, 0);
     rb_define_method(rb_eException, "backtrace", exc_backtrace, 0);
     rb_define_method(rb_eException, "backtrace_locations", exc_backtrace_locations, 0);
@@ -2343,6 +2399,8 @@ Init_Exception(void) https://github.com/ruby/ruby/blob/trunk/ruby_2_5/error.c#L2399
     id_errno = rb_intern_const("errno");
     id_i_path = rb_intern_const("@path");
     id_warn = rb_intern_const("warn");
+    id_top = rb_intern_const("top");
+    id_bottom = rb_intern_const("bottom");
     id_iseq = rb_make_internal_id();
 }
 
Index: ruby_2_5/NEWS
===================================================================
--- ruby_2_5/NEWS	(revision 62904)
+++ ruby_2_5/NEWS	(revision 62905)
@@ -79,6 +79,10 @@ with all sufficient information, see the https://github.com/ruby/ruby/blob/trunk/ruby_2_5/NEWS#L79
 
     * File.lutime  [Feature #4052]
 
+* Exception
+
+  * Exception#full_message takes :highlight and :order options [Bug #14324]
+
 * Hash
 
   * New methods:
Index: ruby_2_5/version.h
===================================================================
--- ruby_2_5/version.h	(revision 62904)
+++ ruby_2_5/version.h	(revision 62905)
@@ -1,10 +1,10 @@ https://github.com/ruby/ruby/blob/trunk/ruby_2_5/version.h#L1
 #define RUBY_VERSION "2.5.1"
-#define RUBY_RELEASE_DATE "2018-03-22"
-#define RUBY_PATCHLEVEL 50
+#define RUBY_RELEASE_DATE "2018-03-24"
+#define RUBY_PATCHLEVEL 51
 
 #define RUBY_RELEASE_YEAR 2018
 #define RUBY_RELEASE_MONTH 3
-#define RUBY_RELEASE_DAY 22
+#define RUBY_RELEASE_DAY 24
 
 #include "ruby/version.h"
 
Index: ruby_2_5/test/ruby/test_exception.rb
===================================================================
--- ruby_2_5/test/ruby/test_exception.rb	(revision 62904)
+++ ruby_2_5/test/ruby/test_exception.rb	(revision 62905)
@@ -1132,5 +1132,27 @@ $stderr = $stdout; raise "\x82\xa0"') do https://github.com/ruby/ruby/blob/trunk/ruby_2_5/test/ruby/test_exception.rb#L1132
 
     _, err2, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; end"], '', true, true)
     assert_equal(err2, out1)
+
+    e = RuntimeError.new("testerror")
+    message = e.full_message(highlight: false)
+    assert_not_match(/\e/, message)
+
+    bt = ["test:100", "test:99", "test:98", "test:1"]
+    e = assert_raise(RuntimeError) {raise RuntimeError, "testerror", bt}
+
+    message = e.full_message(highlight: false, order: :top)
+    assert_not_match(/\e/, message)
+    assert_operator(message.count("\n"), :>, 2)
+    assert_operator(message, :start_with?, "test:100: testerror (RuntimeError)\n")
+    assert_operator(message, :end_with?, "test:1\n")
+
+    message = e.full_message(highlight: false, order: :bottom)
+    assert_not_match(/\e/, message)
+    assert_operator(message.count("\n"), :>, 2)
+    assert_operator(message, :start_with?, "Traceback (most recent call last):")
+    assert_operator(message, :end_with?, "test:100: testerror (RuntimeError)\n")
+
+    message = e.full_message(highlight: true)
+    assert_match(/\e/, message)
   end
 end
Index: ruby_2_5/eval_error.c
===================================================================
--- ruby_2_5/eval_error.c	(revision 62904)
+++ ruby_2_5/eval_error.c	(revision 62905)
@@ -85,12 +85,16 @@ error_print(rb_execution_context_t *ec) https://github.com/ruby/ruby/blob/trunk/ruby_2_5/eval_error.c#L85
     rb_ec_error_print(ec, ec->errinfo);
 }
 
+#define CSI_BEGIN "\033["
+#define CSI_SGR "m"
+
+static const char underline[] = CSI_BEGIN"1;4"CSI_SGR;
+static const char bold[] = CSI_BEGIN"1"CSI_SGR;
+static const char reset[] = CSI_BEGIN""CSI_SGR;
+
 static void
 print_errinfo(const VALUE eclass, const VALUE errat, const VALUE emesg, const VALUE str, int colored)
 {
-    static const char underline[] = "\033[4;1m";
-    static const char bold[] = "\033[1m";
-    static const char reset[] = "\033[m";
     const char *einfo = "";
     long elen = 0;
     VALUE mesg;
@@ -188,7 +192,7 @@ print_backtrace(const VALUE eclass, cons https://github.com/ruby/ruby/blob/trunk/ruby_2_5/eval_error.c#L192
 }
 
 void
-rb_error_write(VALUE errinfo, VALUE errat, VALUE str)
+rb_error_write(VALUE errinfo, VALUE errat, VALUE str, VALUE highlight, VALUE reverse)
 {
     volatile VALUE eclass = Qundef, emesg = Qundef;
 
@@ -205,13 +209,33 @@ rb_error_write(VALUE errinfo, VALUE erra https://github.com/ruby/ruby/blob/trunk/ruby_2_5/eval_error.c#L209
 	    emesg = e;
 	}
     }
-    if (rb_stderr_tty_p()) {
-	write_warn(str, "\033[1mTraceback \033[m(most recent call last):\n");
+    if (NIL_P(reverse) || NIL_P(highlight)) {
+	VALUE tty = (VALUE)rb_stderr_tty_p();
+	if (NIL_P(reverse)) reverse = tty;
+	if (NIL_P(highlight)) highlight = tty;
+    }
+    if (reverse) {
+	static const char traceback[] = "Traceback "
+	    "(most recent call last):\n";
+	const int bold_part = rb_strlen_lit("Traceback");
+	char buff[sizeof(traceback)+sizeof(bold)+sizeof(reset)-2], *p = buff;
+	const char *msg = traceback;
+	long len = sizeof(traceback) - 1;
+	if (highlight) {
+#define APPEND(s, l) (memcpy(p, s, l), p += (l))
+	    APPEND(bold, sizeof(bold)-1);
+	    APPEND(traceback, bold_part);
+	    APPEND(reset, sizeof(reset)-1);
+	    APPEND(traceback + bold_part, sizeof(traceback)-bold_part-1);
+#undef APPEND
+	    len = p - (msg = buff);
+	}
+	write_warn2(str, msg, len);
 	print_backtrace(eclass, errat, str, TRUE);
-	print_errinfo(eclass, errat, emesg, str, TRUE);
+	print_errinfo(eclass, errat, emesg, str, highlight!=0);
     }
     else {
-	print_errinfo(eclass, errat, emesg, str, FALSE);
+	print_errinfo(eclass, errat, emesg, str, highlight!=0);
 	print_backtrace(eclass, errat, str, FALSE);
     }
 }
@@ -231,7 +255,7 @@ rb_ec_error_print(rb_execution_context_t https://github.com/ruby/ruby/blob/trunk/ruby_2_5/eval_error.c#L255
 	errat = rb_get_backtrace(errinfo);
     }
 
-    rb_error_write(errinfo, errat, Qnil);
+    rb_error_write(errinfo, errat, Qnil, Qnil, Qnil);
 
     EC_POP_TAG();
     ec->errinfo = errinfo;
Index: ruby_2_5
===================================================================
--- ruby_2_5	(revision 62904)
+++ ruby_2_5	(revision 62905)

Property changes on: ruby_2_5
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
   Merged /trunk:r62548,62894

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]