console 0.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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