visual_width 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +0 -1
- data/Changes +3 -0
- data/README.md +28 -14
- data/example/table.rb +10 -6
- data/lib/visual_width.rb +21 -10
- data/lib/visual_width/formatter.rb +34 -0
- data/lib/visual_width/table.rb +88 -41
- data/lib/visual_width/version.rb +1 -1
- data/spec/visual_width/table_spec.rb +21 -9
- data/spec/visual_width_spec.rb +20 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 970d02ea37970eae029eb5078fd36eb85e662d9e
|
4
|
+
data.tar.gz: 619d3e14f123e4c9858b9328731209d8fe2f5efd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c531f47569a9c8c4b616be2cf20d6f5bbc8235f61f07ae1e04f02f83863858e47efc73c44d73c5357c51711159c7d4158e2d3e09651ce95d9036e58402ca864
|
7
|
+
data.tar.gz: b2b573865cb4e4ec041f2bc12e5344ac01636dec1a3198273cd1dbf2ba5eb6c723216f436d7d5b72f0ff4e2b6c39677ca69699527a3949bb589343bdf1133cfb
|
data/.rspec
CHANGED
data/Changes
CHANGED
data/README.md
CHANGED
@@ -20,35 +20,48 @@ Or install it yourself as:
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
-
|
24
|
-
# VisualWidth
|
25
|
-
require 'visual_width'
|
23
|
+
## `visual_width`:
|
26
24
|
|
25
|
+
```ruby
|
27
26
|
p VisualWidth.measure("こんにちは") # => 10
|
28
27
|
p VisualWidth.measure("abcdefghij") # => 10
|
29
28
|
|
30
29
|
p VisualWidth.truncate("恋すてふ 我が名はまだき 立ちにけり 人知れずこそ 思ひそめしか", 20) # => "恋すてふ 我が名は..."
|
30
|
+
```
|
31
|
+
|
32
|
+
`.measure()` and `truncate()` methods takes `east_asian: false` option to tell it is not in an East Asian context, regarding ambiguous characters as half-width.
|
33
|
+
See [Ambiguous Characters](http://www.unicode.org/reports/tr11/#Ambiguous) in the report.
|
31
34
|
|
32
|
-
|
35
|
+
## `visual_width/table`:
|
36
|
+
|
37
|
+
```ruby
|
33
38
|
require 'visual_width/table'
|
34
39
|
|
35
40
|
t = VisualWidth::Table.new(
|
36
|
-
|
41
|
+
style: [
|
42
|
+
{ align: :center, width: 8 },
|
43
|
+
{ align: :center, width: 8 },
|
44
|
+
{ align: :right, width: 5 },
|
45
|
+
],
|
37
46
|
)
|
38
47
|
|
39
|
-
header = ['
|
48
|
+
header = ['Nick', 'FullName', 'Age']
|
40
49
|
rows = [
|
41
|
-
['
|
42
|
-
['
|
43
|
-
['
|
50
|
+
['カネダ', '金田 正太郎', 17],
|
51
|
+
['テツオ', '島 鉄雄', 16],
|
52
|
+
['ケイ', '?', 18],
|
44
53
|
]
|
45
|
-
t.
|
54
|
+
puts t.render(rows, header: header)
|
55
|
+
# +--------+--------+-----+
|
56
|
+
# | Nick |FullName| Age |
|
57
|
+
# +--------+--------+-----+
|
58
|
+
# | カネダ |金田 正 | 17|
|
59
|
+
# | | 太郎 | |
|
60
|
+
# | テツオ |島 鉄雄 | 16|
|
61
|
+
# | ケイ | ? | 18|
|
62
|
+
# +--------+--------+-----+
|
46
63
|
```
|
47
64
|
|
48
|
-
Each method can take `east_asian: false` to tell it is not in an East Asian context, regarding ambiguous characters as half-width.
|
49
|
-
|
50
|
-
See [Ambiguous Characters](http://www.unicode.org/reports/tr11/#Ambiguous) in the report.
|
51
|
-
|
52
65
|
## Contributing
|
53
66
|
|
54
67
|
1. Fork it
|
@@ -60,3 +73,4 @@ See [Ambiguous Characters](http://www.unicode.org/reports/tr11/#Ambiguous) in th
|
|
60
73
|
## See Also
|
61
74
|
|
62
75
|
* [unicode-display_width](https://rubygems.org/gems/unicode-display_width) has the same feature as `VisualWidth.measure()` but it extends String class directly and is much slower than `VisualWidth.measure()`
|
76
|
+
* [terminal-table](https://rubygems.org/gems/terminal-table) renders text table, but it cannot deal with East Asian Width characters
|
data/example/table.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
# VisualWidth::Table
|
2
2
|
require 'visual_width/table'
|
3
3
|
t = VisualWidth::Table.new(
|
4
|
-
|
4
|
+
style: [
|
5
|
+
{ align: :center, width: 8 },
|
6
|
+
{ align: :center, width: 8 },
|
7
|
+
{ align: :right, width: 5 },
|
8
|
+
],
|
5
9
|
)
|
6
10
|
|
7
|
-
header = ['
|
11
|
+
header = ['Nick', 'FullName', 'Age']
|
8
12
|
rows = [
|
9
|
-
['
|
10
|
-
['
|
11
|
-
['
|
13
|
+
['カネダ', '金田 正太郎', 17],
|
14
|
+
['テツオ', '島 鉄雄', 16],
|
15
|
+
['ケイ', '?', 18],
|
12
16
|
]
|
13
|
-
puts t.
|
17
|
+
puts t.render(rows, header: header)
|
data/lib/visual_width.rb
CHANGED
@@ -29,25 +29,36 @@ module VisualWidth
|
|
29
29
|
(full_width * 2) + (str.length - full_width)
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
32
|
+
def each_width(str, max_width, east_asian: EAST_ASIAN) # requires block
|
33
33
|
rx = east_asian ? @@t1 : @@t0
|
34
|
-
max = max_length - omission.length
|
35
34
|
pos = 0
|
36
35
|
width = 0
|
37
36
|
str.scan(rx) do |wide,|
|
38
37
|
width += wide ? 2 : 1
|
39
|
-
|
40
|
-
break if width > max
|
41
|
-
|
42
38
|
pos += 1
|
43
39
|
|
44
|
-
|
40
|
+
next_char = str[pos]
|
41
|
+
next_char_width = next_char ? measure(next_char, east_asian: east_asian) : 0
|
42
|
+
if (width + next_char_width) > max_width
|
43
|
+
yield str.slice(0, pos)
|
44
|
+
str = str.slice(pos, str.length)
|
45
|
+
pos = 0
|
46
|
+
width = 0
|
47
|
+
end
|
45
48
|
end
|
49
|
+
if str.length > 0
|
50
|
+
yield str
|
51
|
+
end
|
52
|
+
end
|
46
53
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
str
|
54
|
+
def truncate(str, max_length, omission: '...', east_asian: EAST_ASIAN)
|
55
|
+
max = max_length - omission.length
|
56
|
+
each_width(str, max, east_asian: east_asian) do |line|
|
57
|
+
if line.length == str.length
|
58
|
+
return line
|
59
|
+
else
|
60
|
+
return line + omission
|
61
|
+
end
|
51
62
|
end
|
52
63
|
end
|
53
64
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'visual_width'
|
2
|
+
|
3
|
+
module VisualWidth::Formatter
|
4
|
+
class Align
|
5
|
+
def left(cell, width)
|
6
|
+
align(cell, width) do |line, fill|
|
7
|
+
line + (' ' * fill)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def right(cell, width)
|
12
|
+
align(cell, width) do |line, fill|
|
13
|
+
(' ' * fill) + line
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def center(cell, width)
|
18
|
+
align(cell, width) do |line, fill|
|
19
|
+
half = fill / 2.0
|
20
|
+
(' ' * half.floor) + line + (' ' * half.ceil)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def align(cell, width)
|
27
|
+
w = VisualWidth.measure(cell)
|
28
|
+
if w > width
|
29
|
+
raise ArgumentError, "Invalid width cell: #{cell.inspect}, width: #{width.inspect}"
|
30
|
+
end
|
31
|
+
yield cell, width - w
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/visual_width/table.rb
CHANGED
@@ -1,83 +1,130 @@
|
|
1
1
|
require 'visual_width'
|
2
|
+
require 'visual_width/formatter'
|
2
3
|
|
3
4
|
class VisualWidth::Table
|
4
|
-
|
5
|
+
include VisualWidth
|
5
6
|
|
6
|
-
|
7
|
-
LEFT = -> (cell, fill) {
|
8
|
-
cell + (' ' * fill)
|
9
|
-
}
|
7
|
+
attr_accessor :header, :style
|
10
8
|
|
11
|
-
|
12
|
-
(' ' * fill) + cell
|
13
|
-
}
|
14
|
-
|
15
|
-
CENTER = -> (cell, fill) {
|
16
|
-
half = fill / 2.0
|
17
|
-
(' ' * half.floor) + cell + (' ' * half.ceil)
|
18
|
-
}
|
19
|
-
|
20
|
-
def initialize(header: nil, format: [])
|
9
|
+
def initialize(header: nil, style: [])
|
21
10
|
@header = header
|
22
|
-
@
|
11
|
+
@style = style
|
12
|
+
|
13
|
+
@needs_wrap = @style.any? { |style| style[:width] != nil }
|
23
14
|
end
|
24
15
|
|
25
|
-
def
|
16
|
+
def render(rows, header: nil, output: "")
|
17
|
+
if @needs_wrap
|
18
|
+
default_style = {}
|
19
|
+
rows = rows.map do |row|
|
20
|
+
i = 0
|
21
|
+
row.map do |cell|
|
22
|
+
cell = "#{cell}"
|
23
|
+
width = (@style[i] || default_style)[:width]
|
24
|
+
i += 1
|
25
|
+
if width
|
26
|
+
wrap(cell, width)
|
27
|
+
else
|
28
|
+
cell
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
26
33
|
max_widths = calc_max_widths(rows)
|
27
34
|
h = header || @header
|
35
|
+
style_header = []
|
28
36
|
if h
|
29
|
-
max_widths = h
|
37
|
+
max_widths = calc_max_widths([h])
|
30
38
|
.zip(max_widths)
|
31
39
|
.map { |values| values.max }
|
32
|
-
|
33
|
-
|
34
|
-
|
40
|
+
h.length.times do |i|
|
41
|
+
style = @style[i] || {}
|
42
|
+
|
43
|
+
style_header << style.merge(align: :center)
|
44
|
+
end
|
35
45
|
end
|
36
|
-
|
46
|
+
draw_header(output, max_widths, style_header, h)
|
37
47
|
rows.each do |row|
|
38
|
-
draw_row(output, max_widths, @
|
48
|
+
draw_row(output, max_widths, @style, row)
|
39
49
|
end
|
40
50
|
line(output, max_widths)
|
41
51
|
output
|
42
52
|
end
|
53
|
+
|
54
|
+
def draw(*args)
|
55
|
+
warn "draw() is deprecated. Use render() instead."
|
56
|
+
render(*args)
|
57
|
+
end
|
58
|
+
|
43
59
|
private
|
44
60
|
|
45
|
-
def
|
46
|
-
|
61
|
+
def draw_header(output, max_widths, style, row)
|
62
|
+
line(output, max_widths)
|
63
|
+
|
64
|
+
if row && row.length > 0
|
65
|
+
draw_row(output, max_widths, style, row)
|
47
66
|
line(output, max_widths)
|
48
67
|
end
|
68
|
+
end
|
49
69
|
|
50
|
-
|
51
|
-
|
52
|
-
row.each_with_index do |cell, i|
|
53
|
-
fill(output, max_widths[i], cell.to_s, format[i])
|
54
|
-
output << '|'
|
55
|
-
end
|
56
|
-
output << "\n"
|
70
|
+
def draw_row(output, max_widths, style, row)
|
71
|
+
output << '|'
|
57
72
|
|
58
|
-
|
59
|
-
|
73
|
+
rows = []
|
74
|
+
max_widths.length.times do |i|
|
75
|
+
cell = "#{row[i]}"
|
76
|
+
s = style[i] || {}
|
77
|
+
align = s[:align] || :left
|
78
|
+
width = s[:width] || max_widths[i]
|
79
|
+
c = cell.split(/\n/)
|
80
|
+
if c.length == 0
|
81
|
+
c << ""
|
60
82
|
end
|
83
|
+
output << aligner.send(align, c.shift.strip, width) << '|'
|
84
|
+
if c.length > 0
|
85
|
+
c.each_with_index do |new_cell, row_id|
|
86
|
+
rows[row_id] ||= []
|
87
|
+
rows[row_id][i] = new_cell
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
output << "\n"
|
92
|
+
|
93
|
+
rows.each do |row|
|
94
|
+
draw_row(output, max_widths, style, row)
|
61
95
|
end
|
96
|
+
|
97
|
+
output
|
62
98
|
end
|
63
99
|
|
64
|
-
def line (output,
|
65
|
-
output << '+' <<
|
100
|
+
def line (output, widths)
|
101
|
+
output << '+' << widths.map { |width| '-' * width }.join('+') << "+\n"
|
66
102
|
end
|
67
103
|
|
68
|
-
def
|
69
|
-
|
70
|
-
w = VisualWidth.measure(cell)
|
71
|
-
output << f.call(cell, (max_width - w))
|
104
|
+
def aligner
|
105
|
+
VisualWidth::Formatter::Align.new
|
72
106
|
end
|
73
107
|
|
74
108
|
def calc_max_widths(rows) # -> [max_col0_width, max_col1_width, ...]
|
75
109
|
result = []
|
110
|
+
default_style = {}
|
76
111
|
rows.each_with_index do |row|
|
77
112
|
row.each_with_index do |cell, i|
|
78
|
-
|
113
|
+
ws = "#{cell}".split(/\n/).map do |line|
|
114
|
+
measure(line)
|
115
|
+
end
|
116
|
+
style = @style[i] || default_style
|
117
|
+
result[i] = (ws << (result[i] || 0) << (style[:width] || 0)).max
|
79
118
|
end
|
80
119
|
end
|
81
120
|
result
|
82
121
|
end
|
122
|
+
|
123
|
+
def wrap(cell, width)
|
124
|
+
s = ""
|
125
|
+
each_width(cell.strip, width) do |line|
|
126
|
+
s << line.strip << "\n"
|
127
|
+
end
|
128
|
+
s
|
129
|
+
end
|
83
130
|
end
|
data/lib/visual_width/version.rb
CHANGED
@@ -13,11 +13,11 @@ describe VisualWidth::Table do
|
|
13
13
|
rows
|
14
14
|
end
|
15
15
|
|
16
|
-
context "#
|
17
|
-
it '
|
16
|
+
context "#render" do
|
17
|
+
it 'renders text table' do
|
18
18
|
t = VisualWidth::Table.new
|
19
19
|
|
20
|
-
expect(t.
|
20
|
+
expect(t.render(rows)).to eql(<<-'TEXT')
|
21
21
|
+----------+----------+
|
22
22
|
|りんご |なし |
|
23
23
|
|べにしぐれ|せいぎょく|
|
@@ -26,12 +26,12 @@ describe VisualWidth::Table do
|
|
26
26
|
TEXT
|
27
27
|
end
|
28
28
|
|
29
|
-
it '
|
29
|
+
it 'renders text table with style/CENTER, RIGHT' do
|
30
30
|
t = VisualWidth::Table.new(
|
31
|
-
|
31
|
+
style: [{align: :center}, {align: :right}],
|
32
32
|
)
|
33
33
|
|
34
|
-
expect(t.
|
34
|
+
expect(t.render(rows)).to eql(<<-'TEXT')
|
35
35
|
+----------+----------+
|
36
36
|
| りんご | なし|
|
37
37
|
|べにしぐれ|せいぎょく|
|
@@ -40,11 +40,11 @@ describe VisualWidth::Table do
|
|
40
40
|
TEXT
|
41
41
|
end
|
42
42
|
|
43
|
-
it '
|
43
|
+
it 'renders text table with header/footer' do
|
44
44
|
t = VisualWidth::Table.new(
|
45
45
|
header: %w(A B),
|
46
46
|
)
|
47
|
-
expect(t.
|
47
|
+
expect(t.render(rows)).to eql(<<-'TEXT')
|
48
48
|
+----------+----------+
|
49
49
|
| A | B |
|
50
50
|
+----------+----------+
|
@@ -63,7 +63,7 @@ describe VisualWidth::Table do
|
|
63
63
|
['Average', 93, 96],
|
64
64
|
]
|
65
65
|
|
66
|
-
expect(VisualWidth::Table.new.
|
66
|
+
expect(VisualWidth::Table.new.render(rows, header: header)).to eql(<<-'TEXT')
|
67
67
|
+-------+---------+------+
|
68
68
|
|Student|Mid-Terms|Finals|
|
69
69
|
+-------+---------+------+
|
@@ -74,4 +74,16 @@ describe VisualWidth::Table do
|
|
74
74
|
TEXT
|
75
75
|
end
|
76
76
|
end
|
77
|
+
|
78
|
+
context "#render with wrap" do
|
79
|
+
it "wraps with 10 width" do
|
80
|
+
t = VisualWidth::Table.new(style: [{width: 10}, {width: 10}])
|
81
|
+
expect(t.render([['テレポーター!', '*いしのなかにいる*']])).to eql(<<-'TEXT')
|
82
|
+
+----------+----------+
|
83
|
+
|テレポータ|*いしのな |
|
84
|
+
|ー! |かにいる* |
|
85
|
+
+----------+----------+
|
86
|
+
TEXT
|
87
|
+
end
|
88
|
+
end
|
77
89
|
end
|
data/spec/visual_width_spec.rb
CHANGED
@@ -89,4 +89,24 @@ describe VisualWidth do
|
|
89
89
|
expect(s).to eql('αβγδεζη...')
|
90
90
|
end
|
91
91
|
end
|
92
|
+
|
93
|
+
context ".each_width" do
|
94
|
+
it "splits str in visual width" do
|
95
|
+
str = "恋すてふ_我が名はまだき_立ちにけり_人知れずこそ_思ひそめしか"
|
96
|
+
|
97
|
+
values = []
|
98
|
+
VisualWidth.each_width(str, 10) do |line|
|
99
|
+
values << line
|
100
|
+
end
|
101
|
+
expect(values).to eql(%w(
|
102
|
+
恋すてふ_
|
103
|
+
我が名はま
|
104
|
+
だき_立ち
|
105
|
+
にけり_人
|
106
|
+
知れずこそ
|
107
|
+
_思ひそめ
|
108
|
+
しか
|
109
|
+
))
|
110
|
+
end
|
111
|
+
end
|
92
112
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: visual_width
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fuji, Goro (gfx)
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-10-
|
11
|
+
date: 2013-10-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -56,6 +56,7 @@ files:
|
|
56
56
|
- example/table.rb
|
57
57
|
- lib/visual_width.rb
|
58
58
|
- lib/visual_width/data.rb
|
59
|
+
- lib/visual_width/formatter.rb
|
59
60
|
- lib/visual_width/string_ext.rb
|
60
61
|
- lib/visual_width/string_refine.rb
|
61
62
|
- lib/visual_width/table.rb
|