console 0.5 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,271 +0,0 @@
1
- /*
2
- * console.c -- ruby console library
3
- * Author: William Morgan (mailto: wmorgan-ruby-console@masanjin.net)
4
- * Copyright: Copyright 2010 William Morgan
5
- * License: same terms as Ruby itself
6
- */
7
-
8
- #include <wchar.h>
9
- #include <stdlib.h>
10
- #include <ruby.h>
11
- #include <locale.h>
12
-
13
- #ifdef HAVE_RUBY_ENCODING_H
14
- #include <ruby/encoding.h>
15
- #endif
16
-
17
- static inline int calc_width(char* string, long strlen, long byte_offset, size_t* num_bytes, size_t* num_cols) {
18
- wchar_t wc;
19
- size_t width = -1;
20
- mbstate_t state;
21
-
22
- memset(&state, 0, sizeof(state));
23
- *num_bytes = mbrtowc(&wc, string + byte_offset, strlen - byte_offset, &state);
24
-
25
- if(*num_bytes == (size_t)-2) {
26
- rb_raise(rb_eArgError, "malformed string: incomplete multibyte character at position %ld", byte_offset);
27
- return -1;
28
- }
29
- else if(*num_bytes == (size_t)-1) {
30
- rb_raise(rb_eArgError, "malformed string: invalid multibyte character at position %ld", byte_offset);
31
- return -1;
32
- }
33
- else if(*num_bytes == 0) {
34
- //rb_raise(rb_eArgError, "malformed string: NULL byte at position %ld", byte_offset);
35
- // it's fine to have a NULL byte. forge ahead!
36
- *num_bytes = 1;
37
- *num_cols = 0;
38
- }
39
- else {
40
- *num_cols = wcwidth(wc);
41
- }
42
-
43
- return 0;
44
- }
45
-
46
- /*
47
- * call-seq: init_locale!
48
- *
49
- * Sets the program's current locale from the appropriate environment variables.
50
- * (see `man 3 setlocale` for details).
51
- *
52
- * Equivalent to:
53
- * char* old_locale = setlocale(LC_ALL, NULL);
54
- * return old_locale;
55
- *
56
- * in C.
57
- *
58
- * If you are using Ruby 1.8, you *must* call this at least once before calling
59
- * the other methods in this package. Otherwise, using non-ASCII strings will
60
- * be considered invalid, and #display_width and #display_slice will raise
61
- * ArgumentErrors.
62
- *
63
- * Ruby 1.9 users do not need to call this, since Ruby 1.9 appears to set the
64
- * locale in this manner already. Calling it won't matter, however.
65
- *
66
- * Returns a string representing the old locale. If you wish to change locales
67
- * several times, you can use this value to return to the previous locale.
68
- * Otherwise, just ignore it.
69
- */
70
-
71
- static VALUE init_locale(VALUE v_self) {
72
- char* old_locale = setlocale(LC_ALL, NULL);
73
- setlocale(LC_ALL, ""); // set ctype locale according to appropriate env vars
74
-
75
- return rb_str_new2(old_locale);
76
- }
77
-
78
- /*
79
- * call-seq: display_width(string)
80
- *
81
- * Returns the display width of <code>string</code>, that is, the number of
82
- * columns that the string will take up when printed to screen. This is
83
- * different from both the number of characters and the number of bytes in a
84
- * string.
85
- *
86
- * In Ruby 1.8, the input string is assumed to be in the current locale's
87
- * encoding. If it isn't, an ArgumentError will be raised. Be sure to call
88
- * init_locale! before calling this method! Otherwise every non-ASCII string
89
- * will trigger an ArgumentError.
90
- *
91
- * In Ruby 1.9, the string will be automatically converted from its encoding
92
- * into the current locale's encoding for processing.
93
- *
94
- * Throws an ArgumentError when it encounters an invalid character. On Ruby
95
- * 1.8, this includes any string not in the current locale's encoding. On Ruby
96
- * 1.9, this should only occur if Ruby is unable to convert the string from its
97
- * encoding into the current locale's encoding.
98
- */
99
- static VALUE display_width(VALUE v_self, VALUE v_string) {
100
- Check_Type(v_string, T_STRING);
101
-
102
- #ifdef HAVE_RUBY_ENCODING_H
103
- // convert from whatever encoding it's in.
104
- // TODO: do i have to use rb_protect to relay any exceptions?
105
- v_string = rb_str_encode(v_string, rb_enc_from_encoding(rb_locale_encoding()), 0, Qnil);
106
- #endif
107
-
108
- char* string = RSTRING_PTR(v_string);
109
-
110
- long display_width = 0;
111
- long strlen = RSTRING_LEN(v_string);
112
- long offset = 0;
113
-
114
- while(offset < strlen) {
115
- size_t num_bytes, num_cols;
116
- int err = calc_width(string, strlen, offset, &num_bytes, &num_cols);
117
- if(err) break;
118
-
119
- display_width += num_cols;
120
- offset += num_bytes;
121
- }
122
-
123
- return LONG2NUM(display_width);
124
- }
125
-
126
- static const char* default_pad_string = " ";
127
-
128
- /*
129
- * call-seq:
130
- * display_slice(string, start_offset, display_width=1, pad_string=" ")
131
- *
132
- * Returns a slice of a string, based on display width, rather than character
133
- * or bytes. I.e, the <code>start_offset</code> and <code>display_width</code>
134
- * offsets index the columns required to display the string, not individual
135
- * characters or bytes.
136
- *
137
- * This is useful if you want to display a part of a string on screen, as you
138
- * can pull out a specific portion based on display size.
139
- *
140
- * Padding: slicing can truncate multi-column characters. If the slice
141
- * truncates a character, the string will be padded with
142
- * <code>pad_string</code>, on the left side, right side, or both, as
143
- * necessary. If <code>pad_string</code> is <code>nil</code> then no padding
144
- * will be done. <code>pad_string</code> should be a single-column string for
145
- * this to make sense.
146
- *
147
- * In Ruby 1.8, the input string is assumed to be in the current locale's
148
- * encoding. If it isn't, an ArgumentError will be raised. Be sure to call
149
- * init_locale! before calling this method! Otherwise every non-ASCII string
150
- * will trigger an ArgumentError.
151
- *
152
- * In Ruby 1.9, the string will be automatically converted from its encoding
153
- * into the current locale's encoding. Regardless of the original encoding, the
154
- * returned string will be in the current locale's encoding.
155
- *
156
- * Throws an ArgumentError when it encounters an invalid character. On Ruby
157
- * 1.8, this includes any string not in the current locale's encoding. On Ruby
158
- * 1.9, this should only occur if Ruby is unable to convert the string from its
159
- * encoding into the current locale's encoding.
160
- */
161
- static VALUE display_slice(int argc, VALUE *argv, VALUE v_self) {
162
- VALUE v_string, v_display_start, v_display_width, v_pad_string;
163
- rb_scan_args(argc, argv, "22", &v_string, &v_display_start, &v_display_width, &v_pad_string);
164
- Check_Type(v_string, T_STRING);
165
-
166
- /* try and mimic String#slice's argument handling as much as possible */
167
- int display_start = NUM2INT(v_display_start);
168
- if(display_start < 0) display_start = NUM2LONG(display_width(v_self, v_string)) + display_start; // negative start means from the end of the string
169
- if(display_start < 0) return Qnil; // but if you go too far, you fail
170
-
171
- int display_width;
172
- if(argc < 3) display_width = 1; // default value just like String#slice (although it makes slightly less sense)
173
- else display_width = NUM2INT(v_display_width);
174
- if(display_width < 0) return Qnil; // you fail
175
-
176
- const char* pad_string;
177
- if(argc < 4) pad_string = default_pad_string; // only fill in default if unspecified; nil is a valid value
178
- else if(v_pad_string == Qnil) pad_string = "";
179
- else pad_string = RSTRING_PTR(v_pad_string);
180
-
181
- #ifdef HAVE_RUBY_ENCODING_H
182
- // TODO: do i have to use rb_protect to relay any exceptions?
183
- v_string = rb_str_encode(v_string, rb_enc_from_encoding(rb_locale_encoding()), 0, Qnil);
184
- #endif
185
- char* string = RSTRING_PTR(v_string);
186
- long slen = RSTRING_LEN(v_string);
187
-
188
- // first, advance the string pointer so that we've seen display_start width characters
189
- long current_width = 0;
190
- long offset = 0;
191
- while((offset < slen) && (current_width < display_start)) {
192
- size_t num_bytes, num_cols;
193
- int err = calc_width(string, slen, offset, &num_bytes, &num_cols);
194
-
195
- current_width += num_cols;
196
- offset += num_bytes;
197
- }
198
-
199
- /* here's a weird behavior (to me!) of String#slice that we emulate:
200
- * if the start point is the string length itself, you get an empty
201
- * string back; if the start point is greater than that, you get nil.
202
- */
203
- if((current_width < display_start)) return Qnil;
204
-
205
- /* determine left padding */
206
- const char* pad_left = "";
207
- if((current_width > display_start) && (display_width > 0)) pad_left = pad_string;
208
-
209
- // now, advance the string_end pointer so that we've seen an additional display_width width characters
210
- long end_offset = offset;
211
- current_width -= display_start;
212
- while((end_offset < slen) && (current_width < display_width)) {
213
- size_t num_bytes, num_cols;
214
- int err = calc_width(string, slen, end_offset, &num_bytes, &num_cols);
215
-
216
- if((current_width + num_cols) > (size_t)display_width) break; // have to stop here
217
-
218
- current_width += num_cols;
219
- end_offset += num_bytes;
220
- }
221
-
222
- /* determine right padding */
223
- const char* pad_right = "";
224
- if((current_width < display_width) && (end_offset < slen)) pad_right = pad_string;
225
-
226
- // finally, construct a new string
227
- int bytesize = end_offset - offset;
228
- int leftsize = strlen(pad_left);
229
- int rightsize = strlen(pad_right);
230
-
231
- char* new_string = calloc(bytesize + leftsize + rightsize + 1, sizeof(char));
232
- if(leftsize > 0) strcpy(new_string, pad_left);
233
- if(bytesize > 0) memcpy(new_string + leftsize, string + offset, bytesize * sizeof(char));
234
- if(rightsize > 0) strcpy(new_string + leftsize + bytesize, pad_right);
235
-
236
- (new_string + bytesize + leftsize + rightsize)[0] = 0;
237
-
238
- #ifdef HAVE_RUBY_ENCODING_H
239
- return rb_enc_str_new(new_string, bytesize + leftsize + rightsize, rb_enc_get(v_string));
240
- #else
241
- return rb_str_new(new_string, bytesize + leftsize + rightsize);
242
- #endif
243
- }
244
-
245
- /*
246
- * A helper class for console-based programs that need to deal with non-ASCII
247
- * code. If you are writing a curses/ncurses program, or otherwise care about
248
- * the display width of characters on the screen, this is crucial stuff.
249
- *
250
- * Provides:
251
- *
252
- * Console.init_locale!: set the program's locale from the appropriate
253
- * environment variables. (Ruby 1.8 programs must call this before calling any
254
- * of the other methods. Ruby 1.9 programs can call it or skip it without
255
- * effect.)
256
- *
257
- * Console.display_width: calculates the display width of a string
258
- *
259
- * Console.display_slice: returns a substring according to display offset
260
- * and display width parameters.
261
- *
262
- */
263
-
264
- void Init_console() {
265
- VALUE cConsole;
266
-
267
- cConsole = rb_define_class("Console", rb_cObject);
268
- rb_define_module_function(cConsole, "display_width", display_width, 1);
269
- rb_define_module_function(cConsole, "display_slice", display_slice, -1);
270
- rb_define_module_function(cConsole, "init_locale!", init_locale, 0);
271
- }
@@ -1,2 +0,0 @@
1
- require 'mkmf'
2
- create_makefile("console")
@@ -1,37 +0,0 @@
1
- # encoding: utf-8
2
-
3
- ## lib/console/string.rb -- include Console methods into String
4
- ## Author: William Morgan (mailto: wmorgan-ruby-console@masanjin.net)
5
- ## Copyright: Copyright 2010 William Morgan
6
- ## License: same terms as Ruby itself
7
-
8
- require 'console'
9
-
10
- ## reopen the String class and add #display_width and
11
- ## #display_slice methods directly to strings.
12
- ##
13
- ## If you include "console/string", you can call
14
- ## "能吞a".display_width
15
- ## instead of
16
- ## Console.display_width "能吞a"
17
- ##
18
- ## and
19
- ##
20
- ## "能吞a".display_slice 0, 2
21
- ## instead of
22
- ## Console.display_slice "能吞a", 0, 2
23
- ##
24
-
25
- class String
26
- ## Returns the display width of the string. See Console.display_width for details.
27
- def display_width; Console.display_width self end
28
-
29
- ## Returns a substring according to display-based start and offset values. See
30
- ## Console.display_slice for what this means.
31
- ##
32
- ## :call-seq:
33
- ## display_slice(start, offset=1, pad_string=" ")
34
- ##
35
- ## (rdoc fail)
36
- def display_slice(*a); Console.display_slice self, *a end
37
- end
@@ -1,126 +0,0 @@
1
- # encoding: utf-8
2
-
3
- ## test/console.rb -- unit tests for ruby Console library
4
- ## Author: William Morgan (mailto: wmorgan-ruby-console@masanjin.net)
5
- ## Copyright: Copyright 2010 William Morgan
6
- ## License: same terms as Ruby itself
7
-
8
- require 'test/unit'
9
- require 'console'
10
-
11
- Console.init_locale!
12
-
13
- class ConsoleTest < ::Test::Unit::TestCase
14
- def setup
15
- @s = "能吞aê"
16
- end
17
-
18
- def test_slice_of_zero_width_is_empty_string
19
- assert_equal "", Console.display_slice(@s, 0, 0)
20
- assert_equal "", Console.display_slice(@s, 1, 0)
21
- end
22
-
23
- def test_slice_out_of_bounds_is_nil
24
- assert_equal nil, Console.display_slice(@s, 100, 3)
25
- assert_equal nil, Console.display_slice(@s, -100, 3)
26
- end
27
-
28
- def test_slice_with_negative_offset
29
- assert_equal "ê", Console.display_slice(@s, -1, 1)
30
- assert_equal "aê", Console.display_slice(@s, -2, 2)
31
- assert_equal "a", Console.display_slice(@s, -2, 1)
32
- end
33
-
34
- def test_slice_width_argument_defaults_to_1
35
- assert_equal "ê", Console.display_slice(@s, -1)
36
- assert_equal "a", Console.display_slice(@s, -2)
37
- end
38
-
39
- def test_slice_works_on_chinese_characters
40
- assert_equal "能", Console.display_slice(@s, 0, 2);
41
- assert_equal "能吞", Console.display_slice(@s, 0, 4);
42
- assert_equal "能吞a", Console.display_slice(@s, 0, 5);
43
- assert_equal "能吞aê", Console.display_slice(@s, 0, 6);
44
- end
45
-
46
- def test_slice_with_excessive_width_is_still_cool
47
- assert_equal "能吞aê", Console.display_slice(@s, 0, 100);
48
- assert_equal "吞aê", Console.display_slice(@s, 2, 100);
49
- assert_equal "aê", Console.display_slice(@s, 4, 100);
50
- assert_equal "ê", Console.display_slice(@s, 5, 100);
51
- assert_equal "", Console.display_slice(@s, 6, 100); # yep, we get a non-nil at this value
52
- assert_equal nil, Console.display_slice(@s, 7, 100);
53
- end
54
-
55
- def test_slice_with_the_biggest_valid_start_offset_behaves_like_String_slice_does
56
- assert_equal "", Console.display_slice(@s, 6, 100);
57
- assert_equal "", Console.display_slice(@s, 6, 0);
58
- assert_equal nil, Console.display_slice(@s, 6, -1);
59
- end
60
-
61
- def test_slice_misaligned_start_offsets_get_padded
62
- assert_equal "", Console.display_slice(@s, 0, 0)
63
- assert_equal " ", Console.display_slice(@s, 0, 1)
64
- assert_equal "能", Console.display_slice(@s, 0, 2)
65
- assert_equal "能 ", Console.display_slice(@s, 0, 3)
66
-
67
- assert_equal "", Console.display_slice(@s, 1, 0)
68
- assert_equal " ", Console.display_slice(@s, 1, 1)
69
- assert_equal " ", Console.display_slice(@s, 1, 2)
70
- assert_equal " 吞", Console.display_slice(@s, 1, 3)
71
-
72
- assert_equal "", Console.display_slice(@s, 3, 0);
73
- assert_equal " ", Console.display_slice(@s, 3, 1);
74
- assert_equal " a", Console.display_slice(@s, 3, 2);
75
- assert_equal " aê", Console.display_slice(@s, 3, 3);
76
- end
77
-
78
- def test_slice_misaligned_start_offsets_get_padded_with_specified_character
79
- assert_equal "", Console.display_slice(@s, 0, 0, "X")
80
- assert_equal "X", Console.display_slice(@s, 0, 1, "X")
81
- assert_equal "XX", Console.display_slice(@s, 1, 2, "X")
82
- end
83
-
84
- def test_slice_fails_on_nonstrings
85
- assert_raises(TypeError) { Console.display_slice :potato, 1, 1 }
86
- assert_raises(TypeError) { Console.display_slice 3, 1, 1 }
87
- end
88
-
89
- def test_display_width_empty_string_is_zero
90
- assert_equal 0, Console.display_width("")
91
- end
92
-
93
- def test_display_width_works_on_ASCII_strings
94
- assert_equal 1, Console.display_width("a")
95
- assert_equal 1, Console.display_width(" ")
96
- assert_equal 6, Console.display_width("potato")
97
- end
98
-
99
- def test_display_width_works_on_accented_characters
100
- assert_equal 1, Console.display_width("ê")
101
- assert_equal 4, Console.display_width("êêêê")
102
- end
103
-
104
- def test_display_width_works_on_chinese_characters
105
- assert_equal 2, Console.display_width("能")
106
- assert_equal 4, Console.display_width("能吞")
107
- end
108
-
109
- def test_display_width_works_on_mixed_stuff
110
- assert_equal 2, Console.display_width("aê")
111
- assert_equal 5, Console.display_width("能吞a")
112
- assert_equal 6, Console.display_width("能吞aê")
113
- assert_equal 6, Console.display_width("aê能吞")
114
- assert_equal 6, Console.display_width("a能ê吞")
115
- assert_equal 6, Console.display_width("能aê吞")
116
- end
117
-
118
- def test_display_width_fails_on_nonstrings
119
- assert_raises(TypeError) { Console.display_width :potato }
120
- assert_raises(TypeError) { Console.display_width 3 }
121
- end
122
-
123
- def test_display_width_of_a_big_crazy_string
124
- assert_equal 154, Console.display_width("我能吞下玻璃而不傷身體。Góa ē-tàng chia̍h po-lê, mā bē tio̍h-siong.私はガラスを食べられます。それは私を傷つけません。I can eat glass and it doesn't hurt me.")
125
- end
126
- end