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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4de483063c67cddbaca1f3ac3a1ac27536311218
4
- data.tar.gz: 3d9ab9b361e41e1edc9d2f095c28a3591e72cfbb
3
+ metadata.gz: 970d02ea37970eae029eb5078fd36eb85e662d9e
4
+ data.tar.gz: 619d3e14f123e4c9858b9328731209d8fe2f5efd
5
5
  SHA512:
6
- metadata.gz: fd041e918922cf48e5686b8d9d7c4417b8bbde49c11fa65f6f2d3c6783f0afccd6f41397d2aae6b75f94ed3cdba3e3fbdd04901433df06ee1ee935ed724b68e0
7
- data.tar.gz: 40a336d2d2e0e2b919109744910a17cb2860dfe13e59c69c7766c3707f65a614e8226402c1bbf5711953f81a154f1022c3c7e8df7bc93b76755d54fcd429f815
6
+ metadata.gz: 1c531f47569a9c8c4b616be2cf20d6f5bbc8235f61f07ae1e04f02f83863858e47efc73c44d73c5357c51711159c7d4158e2d3e09651ce95d9036e58402ca864
7
+ data.tar.gz: b2b573865cb4e4ec041f2bc12e5344ac01636dec1a3198273cd1dbf2ba5eb6c723216f436d7d5b72f0ff4e2b6c39677ca69699527a3949bb589343bdf1133cfb
data/.rspec CHANGED
@@ -1,2 +1 @@
1
1
  --color
2
- --format doc
data/Changes CHANGED
@@ -1,4 +1,7 @@
1
1
  This is a revision history of visual_width gem.
2
2
 
3
+ 0.0.2 2013-10-26 08:51:02+0900
4
+ - Added visual_width/table.rb to draw text tables
5
+
3
6
  0.0.1 2013-10-24 00:22:53+0900
4
7
  - This is the initial release of visual_width
data/README.md CHANGED
@@ -20,35 +20,48 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- ```ruby
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
- # VisualWidth::Table
35
+ ## `visual_width/table`:
36
+
37
+ ```ruby
33
38
  require 'visual_width/table'
34
39
 
35
40
  t = VisualWidth::Table.new(
36
- format: [VisualWidth::Table::LEFT, VisualWidth::Table::RIGHT, VisualWidth::Table::RIGHT]
41
+ style: [
42
+ { align: :center, width: 8 },
43
+ { align: :center, width: 8 },
44
+ { align: :right, width: 5 },
45
+ ],
37
46
  )
38
47
 
39
- header = ['Student', 'Mid-Terms', 'Finals']
48
+ header = ['Nick', 'FullName', 'Age']
40
49
  rows = [
41
- ['Sam', 94, 93],
42
- ['Jane', 92, 99],
43
- ['Average', 93, 96],
50
+ ['カネダ', '金田 正太郎', 17],
51
+ ['テツオ', '島 鉄雄', 16],
52
+ ['ケイ', '?', 18],
44
53
  ]
45
- t.draw(rows, header: header)
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
- format: [VisualWidth::Table::LEFT, VisualWidth::Table::RIGHT, VisualWidth::Table::RIGHT]
4
+ style: [
5
+ { align: :center, width: 8 },
6
+ { align: :center, width: 8 },
7
+ { align: :right, width: 5 },
8
+ ],
5
9
  )
6
10
 
7
- header = ['Student', 'Mid-Terms', 'Finals']
11
+ header = ['Nick', 'FullName', 'Age']
8
12
  rows = [
9
- ['Sam', 94, 93],
10
- ['Jane', 92, 99],
11
- ['Average', 93, 96],
13
+ ['カネダ', '金田 正太郎', 17],
14
+ ['テツオ', '島 鉄雄', 16],
15
+ ['ケイ', '?', 18],
12
16
  ]
13
- puts t.draw(rows, header: header)
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 truncate(str, max_length, omission: '...', east_asian: EAST_ASIAN)
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
- break if width == max
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
- if width < str.length
48
- str.slice(0, pos) + omission
49
- else
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
@@ -1,83 +1,130 @@
1
1
  require 'visual_width'
2
+ require 'visual_width/formatter'
2
3
 
3
4
  class VisualWidth::Table
4
- attr_accessor :header, :footer, :format
5
+ include VisualWidth
5
6
 
6
- # align (left, right, center)
7
- LEFT = -> (cell, fill) {
8
- cell + (' ' * fill)
9
- }
7
+ attr_accessor :header, :style
10
8
 
11
- RIGHT = -> (cell, fill) {
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
- @format = format
11
+ @style = style
12
+
13
+ @needs_wrap = @style.any? { |style| style[:width] != nil }
23
14
  end
24
15
 
25
- def draw(rows, header: nil, output: "")
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.map { |cell| VisualWidth.measure(cell) }
37
+ max_widths = calc_max_widths([h])
30
38
  .zip(max_widths)
31
39
  .map { |values| values.max }
32
- format_header = [CENTER] * h.length
33
- else
34
- format_header = nil
40
+ h.length.times do |i|
41
+ style = @style[i] || {}
42
+
43
+ style_header << style.merge(align: :center)
44
+ end
35
45
  end
36
- draw_row(output, max_widths, format_header, h, separated: true)
46
+ draw_header(output, max_widths, style_header, h)
37
47
  rows.each do |row|
38
- draw_row(output, max_widths, @format, row)
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 draw_row(output, max_widths, format, row, separated: false)
46
- if separated
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
- if row
51
- output << '|'
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
- if separated
59
- line(output, max_widths)
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, max_widths)
65
- output << '+' << max_widths.map { |width| '-' * width }.join('+') << "+\n"
100
+ def line (output, widths)
101
+ output << '+' << widths.map { |width| '-' * width }.join('+') << "+\n"
66
102
  end
67
103
 
68
- def fill(output, max_width, cell, f)
69
- f ||= LEFT
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
- result[i] = [result[i] || 0, VisualWidth.measure(cell.to_s)].max
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
@@ -1,3 +1,3 @@
1
1
  module VisualWidth
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -13,11 +13,11 @@ describe VisualWidth::Table do
13
13
  rows
14
14
  end
15
15
 
16
- context "#draw" do
17
- it 'draws text table' do
16
+ context "#render" do
17
+ it 'renders text table' do
18
18
  t = VisualWidth::Table.new
19
19
 
20
- expect(t.draw(rows)).to eql(<<-'TEXT')
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 'draws text table with formatter/CENTER, RIGHT' do
29
+ it 'renders text table with style/CENTER, RIGHT' do
30
30
  t = VisualWidth::Table.new(
31
- format: [VisualWidth::Table::CENTER, VisualWidth::Table::RIGHT],
31
+ style: [{align: :center}, {align: :right}],
32
32
  )
33
33
 
34
- expect(t.draw(rows)).to eql(<<-'TEXT')
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 'draws text table with header/footer' do
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.draw(rows)).to eql(<<-'TEXT')
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.draw(rows, header: header)).to eql(<<-'TEXT')
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
@@ -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.2
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-24 00:00:00.000000000 Z
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