visual_width 0.0.2 → 0.0.3
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.
- 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
|