kosi 0.0.1
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 +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.rubocop.yml +12 -0
- data/.travis.yml +8 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +22 -0
- data/README.html +273 -0
- data/README.md +290 -0
- data/Rakefile +8 -0
- data/kosi.gemspec +28 -0
- data/lib/kosi.rb +13 -0
- data/lib/kosi/align.rb +33 -0
- data/lib/kosi/char_option.rb +17 -0
- data/lib/kosi/connector_char.rb +26 -0
- data/lib/kosi/header.rb +18 -0
- data/lib/kosi/horizontal_border_char.rb +26 -0
- data/lib/kosi/options.rb +15 -0
- data/lib/kosi/separate_each_row.rb +15 -0
- data/lib/kosi/validators.rb +3 -0
- data/lib/kosi/validators/each_array_length_validator.rb +21 -0
- data/lib/kosi/validators/row_type_validator.rb +19 -0
- data/lib/kosi/version.rb +4 -0
- data/lib/kosi/vertical_border_char.rb +26 -0
- data/lib/table.rb +138 -0
- data/spec/kosi/align_spec.rb +77 -0
- data/spec/kosi/connector_char_spec.rb +59 -0
- data/spec/kosi/header_spec.rb +93 -0
- data/spec/kosi/horizontal_border_char_spec.rb +59 -0
- data/spec/kosi/separate_each_row_spec.rb +55 -0
- data/spec/kosi/validators/each_array_length_validator_spec.rb +56 -0
- data/spec/kosi/validators/row_type_validator_spec.rb +51 -0
- data/spec/kosi/vertical_border_char_spec.rb +59 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/table_spec.rb +134 -0
- metadata +173 -0
data/Rakefile
ADDED
data/kosi.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'kosi/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'kosi'
|
8
|
+
spec.version = Kosi::VERSION
|
9
|
+
spec.authors = ['tbpgr']
|
10
|
+
spec.email = ['tbpgr@tbpgr.jp']
|
11
|
+
spec.summary = %q(terminal table format for japanese)
|
12
|
+
spec.description = %q(terminal table format for japanese)
|
13
|
+
spec.homepage = 'https://github.com/tbpgr/kosi'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_runtime_dependency 'activesupport', '~> 4.0.1'
|
22
|
+
spec.add_runtime_dependency 'activemodel', '~> 4.0.2'
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
25
|
+
spec.add_development_dependency 'rake'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 2.14.1'
|
27
|
+
spec.add_development_dependency 'simplecov', '~> 0.8.2'
|
28
|
+
end
|
data/lib/kosi.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'kosi/align'
|
3
|
+
require 'kosi/connector_char'
|
4
|
+
require 'kosi/header'
|
5
|
+
require 'kosi/horizontal_border_char'
|
6
|
+
require 'kosi/options'
|
7
|
+
require 'kosi/separate_each_row'
|
8
|
+
require 'kosi/vertical_border_char'
|
9
|
+
require 'table'
|
10
|
+
|
11
|
+
# TableFormat for Terminal(Use Japanese Charactors)
|
12
|
+
module Kosi
|
13
|
+
end
|
data/lib/kosi/align.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'kosi/options'
|
3
|
+
|
4
|
+
# TableFormat for Terminal(Use Japanese Charactors)
|
5
|
+
module Kosi
|
6
|
+
# Align
|
7
|
+
class Align
|
8
|
+
attr_reader :value
|
9
|
+
# Align Type
|
10
|
+
module TYPE
|
11
|
+
CENTER = :center
|
12
|
+
RIGHT = :right
|
13
|
+
LEFT = :left
|
14
|
+
DEFAULT = LEFT
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(options)
|
18
|
+
@value = options[OptionKeys::ALIGN] || TYPE::DEFAULT
|
19
|
+
end
|
20
|
+
|
21
|
+
def apply(text, max_value, diff)
|
22
|
+
pos = max_value - diff
|
23
|
+
case @value
|
24
|
+
when TYPE::CENTER
|
25
|
+
text.center(pos)
|
26
|
+
when TYPE::RIGHT
|
27
|
+
text.rjust(pos)
|
28
|
+
when TYPE::LEFT
|
29
|
+
text.ljust(pos)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'kosi/options'
|
2
|
+
|
3
|
+
# TableFormat for Terminal(Use Japanese Charactors)
|
4
|
+
module Kosi
|
5
|
+
# CharOption
|
6
|
+
class CharOption
|
7
|
+
attr_reader :value
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
@value = String(options[key])
|
11
|
+
@value = default if @value.empty?
|
12
|
+
unless @value.size == 1
|
13
|
+
fail ArgumentError, format(invalid_char_msg, @value.size, @value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'kosi/options'
|
2
|
+
require 'kosi/char_option'
|
3
|
+
|
4
|
+
# TableFormat for Terminal(Use Japanese Charactors)
|
5
|
+
module Kosi
|
6
|
+
# ConnectorChar
|
7
|
+
class ConnectorChar < CharOption
|
8
|
+
DEFAULT = '+'
|
9
|
+
INVALID_CHAR_MSG = \
|
10
|
+
'connector char must be 1 length(%d) char %s'
|
11
|
+
|
12
|
+
def default
|
13
|
+
DEFAULT
|
14
|
+
end
|
15
|
+
|
16
|
+
def key
|
17
|
+
OptionKeys::CONNECTOR_CHAR
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def invalid_char_msg
|
23
|
+
INVALID_CHAR_MSG
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/kosi/header.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'kosi/options'
|
3
|
+
|
4
|
+
# TableFormat for Terminal(Use Japanese Charactors)
|
5
|
+
module Kosi
|
6
|
+
# Header
|
7
|
+
class Header
|
8
|
+
attr_reader :value
|
9
|
+
|
10
|
+
def initialize(options)
|
11
|
+
@value = options[OptionKeys::HEADER]
|
12
|
+
end
|
13
|
+
|
14
|
+
def empty?
|
15
|
+
@value.nil?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'kosi/options'
|
2
|
+
require 'kosi/char_option'
|
3
|
+
|
4
|
+
# TableFormat for Terminal(Use Japanese Charactors)
|
5
|
+
module Kosi
|
6
|
+
# HorizontalBorderChar
|
7
|
+
class HorizontalBorderChar < CharOption
|
8
|
+
DEFAULT = '-'
|
9
|
+
INVALID_CHAR_MSG = \
|
10
|
+
'horizontal_border_char must be 1 length(%d) char %s'
|
11
|
+
|
12
|
+
def default
|
13
|
+
DEFAULT
|
14
|
+
end
|
15
|
+
|
16
|
+
def key
|
17
|
+
OptionKeys::HORIZONTAL_BORDER_CHAR
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def invalid_char_msg
|
23
|
+
INVALID_CHAR_MSG
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/kosi/options.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# TableFormat for Terminal(Use Japanese Charactors)
|
4
|
+
module Kosi
|
5
|
+
# Options
|
6
|
+
module OptionKeys
|
7
|
+
ALIGN = :align
|
8
|
+
HEADER = :header
|
9
|
+
HAS_HEADER = :has_header
|
10
|
+
CONNECTOR_CHAR = :connector_char
|
11
|
+
HORIZONTAL_BORDER_CHAR = :horizontal_border_char
|
12
|
+
VERTICAL_BORDER_CHAR = :vertical_border_char
|
13
|
+
SEPARATE_EACH_ROW = :separate_each_row
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'kosi/options'
|
3
|
+
|
4
|
+
# TableFormat for Terminal(Use Japanese Charactors)
|
5
|
+
module Kosi
|
6
|
+
# SeparateEachRow
|
7
|
+
class SeparateEachRow
|
8
|
+
attr_reader :value
|
9
|
+
DEFAULT = false
|
10
|
+
|
11
|
+
def initialize(options)
|
12
|
+
@value = options[OptionKeys::SEPARATE_EACH_ROW] || DEFAULT
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Kosi
|
4
|
+
module Kosi
|
5
|
+
# Validators
|
6
|
+
module Validators
|
7
|
+
# EachArrayLength Validator
|
8
|
+
# if row class is not Array, fail ArgumentError.
|
9
|
+
class EachArrayLength
|
10
|
+
INVALID_ARRAY_LENGTH_MESSAGE = \
|
11
|
+
'invalid array length.each array must be same length'
|
12
|
+
def self.validate(row)
|
13
|
+
row_sizes = row.map(&:size)
|
14
|
+
return if row_sizes.uniq.size == 0
|
15
|
+
unless row_sizes.uniq.size == 1
|
16
|
+
fail ArgumentError, INVALID_ARRAY_LENGTH_MESSAGE
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Kosi
|
4
|
+
module Kosi
|
5
|
+
# Validators
|
6
|
+
module Validators
|
7
|
+
# RowType Validator
|
8
|
+
# if row class is not Array, fail ArgumentError.
|
9
|
+
class RowType
|
10
|
+
INVALID_ROW_MESSAGE = 'invalid row class %s. row class must be Array.'
|
11
|
+
def self.validate(row)
|
12
|
+
unless row.is_a?(Array)
|
13
|
+
message = format(INVALID_ROW_MESSAGE, row.class)
|
14
|
+
fail ArgumentError, message
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/kosi/version.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'kosi/options'
|
2
|
+
require 'kosi/char_option'
|
3
|
+
|
4
|
+
# TableFormat for Terminal(Use Japanese Charactors)
|
5
|
+
module Kosi
|
6
|
+
# VerticalBorderChar
|
7
|
+
class VerticalBorderChar < CharOption
|
8
|
+
DEFAULT = '|'
|
9
|
+
INVALID_CHAR_MSG = \
|
10
|
+
'vertical_border_char must be 1 length(%d) char %s'
|
11
|
+
|
12
|
+
def default
|
13
|
+
DEFAULT
|
14
|
+
end
|
15
|
+
|
16
|
+
def key
|
17
|
+
OptionKeys::VERTICAL_BORDER_CHAR
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def invalid_char_msg
|
23
|
+
INVALID_CHAR_MSG
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/table.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'kosi'
|
3
|
+
require 'kosi/validators'
|
4
|
+
|
5
|
+
# TableFormat for Terminal(Use Japanese Charactors)
|
6
|
+
module Kosi
|
7
|
+
# Table
|
8
|
+
class Table
|
9
|
+
attr_reader :align, :header, :connector_char, :horizontal_border_char,
|
10
|
+
:vertical_border_char, :separate_each_row
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@align = Kosi::Align.new(options)
|
14
|
+
@header = Kosi::Header.new(options)
|
15
|
+
@connector_char = Kosi::ConnectorChar.new(options)
|
16
|
+
@horizontal_border_char = Kosi::HorizontalBorderChar.new(options)
|
17
|
+
@vertical_border_char = Kosi::VerticalBorderChar.new(options)
|
18
|
+
@separate_each_row = Kosi::SeparateEachRow.new(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def render(rows)
|
22
|
+
check_rows_type(rows)
|
23
|
+
check_each_array_length(rows)
|
24
|
+
header = read_header
|
25
|
+
max_lengths = get_max_lengths(rows, header)
|
26
|
+
table_format_rows = get_table_format_rows(rows, max_lengths, header)
|
27
|
+
table_format_rows.join("\n") + "\n"
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def check_rows_type(rows)
|
33
|
+
Validators::RowType.validate(rows)
|
34
|
+
rows.each { |row|Validators::RowType.validate(row) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def check_each_array_length(rows)
|
38
|
+
Validators::EachArrayLength.validate(rows)
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_max_lengths(rows, header)
|
42
|
+
tmp_rows = rows.dup
|
43
|
+
tmp_rows << header unless @header.empty?
|
44
|
+
tmp_rows.reduce([]) do |max_lengths, row|
|
45
|
+
row.each_with_index do |e, i|
|
46
|
+
s = ascii1_other2_size(String(e))
|
47
|
+
next unless greater?(s, max_lengths[i])
|
48
|
+
max_lengths[i] = s
|
49
|
+
end
|
50
|
+
max_lengths
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def greater?(one, other)
|
55
|
+
return true if other.nil?
|
56
|
+
one > other
|
57
|
+
end
|
58
|
+
|
59
|
+
def ascii1_other2_size(column)
|
60
|
+
column.split('').reduce(0) do |a, e|
|
61
|
+
a += half?(e) ? 1 : 2
|
62
|
+
a
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def half?(char)
|
67
|
+
case char.ord
|
68
|
+
when 1..127, 65_377..65_439
|
69
|
+
true
|
70
|
+
else
|
71
|
+
false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_table_format_rows(rows, max_lengths, header)
|
76
|
+
top_bottom_line = top_bottom(max_lengths)
|
77
|
+
results = [top_bottom_line]
|
78
|
+
append_header(results, header, max_lengths, top_bottom_line)
|
79
|
+
rows.each do |col|
|
80
|
+
results += formated_row(col, max_lengths, top_bottom_line)
|
81
|
+
end
|
82
|
+
results << top_bottom_line unless @separate_each_row.value
|
83
|
+
results
|
84
|
+
end
|
85
|
+
|
86
|
+
def formated_row(col, max_lengths, top_bottom_line)
|
87
|
+
results = []
|
88
|
+
row_results = get_columns(col, max_lengths)
|
89
|
+
virtical = @vertical_border_char.value
|
90
|
+
results << "#{virtical}#{row_results.join(virtical)}#{virtical}"
|
91
|
+
results << top_bottom_line if @separate_each_row.value
|
92
|
+
results
|
93
|
+
end
|
94
|
+
|
95
|
+
def append_header(results, header, max_lengths, top_bottom_line)
|
96
|
+
unless @header.empty?
|
97
|
+
results << formated_header(header, max_lengths)
|
98
|
+
results << top_bottom_line
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def top_bottom(max_lengths)
|
103
|
+
results = max_lengths.reduce([]) do |ret, column_size|
|
104
|
+
ret << @horizontal_border_char.value * column_size
|
105
|
+
ret
|
106
|
+
end
|
107
|
+
con = @connector_char.value
|
108
|
+
"#{con}#{results.join(con)}#{con}"
|
109
|
+
end
|
110
|
+
|
111
|
+
def read_header
|
112
|
+
return nil if @header.empty?
|
113
|
+
@header.value
|
114
|
+
end
|
115
|
+
|
116
|
+
def formated_header(header, max_lengths)
|
117
|
+
header_results = []
|
118
|
+
header.size.times do |i|
|
119
|
+
column = String(header[i])
|
120
|
+
diff = ascii1_other2_size(column) - column.size
|
121
|
+
header_results << @align.apply(column, max_lengths[i], diff)
|
122
|
+
end
|
123
|
+
virtical = @vertical_border_char.value
|
124
|
+
header_line = header_results.join(virtical)
|
125
|
+
"#{virtical}#{header_line}#{virtical}"
|
126
|
+
end
|
127
|
+
|
128
|
+
def get_columns(columns, max_lengths)
|
129
|
+
column_results = []
|
130
|
+
columns.size.times do |i|
|
131
|
+
column = String(columns[i])
|
132
|
+
diff = ascii1_other2_size(column) - column.size
|
133
|
+
column_results << @align.apply(column, max_lengths[i], diff)
|
134
|
+
end
|
135
|
+
column_results
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'kosi'
|
4
|
+
|
5
|
+
describe Kosi::Align do
|
6
|
+
context :apply do
|
7
|
+
cases = [
|
8
|
+
{
|
9
|
+
case_no: 1,
|
10
|
+
case_title: 'no options case',
|
11
|
+
options: {},
|
12
|
+
text: 'h',
|
13
|
+
max_value: 3,
|
14
|
+
diff: 0,
|
15
|
+
expected: Kosi::Align::TYPE::LEFT,
|
16
|
+
expected: 'h '
|
17
|
+
},
|
18
|
+
{
|
19
|
+
case_no: 2,
|
20
|
+
case_title: 'align options right case',
|
21
|
+
options: { Kosi::OptionKeys::ALIGN => Kosi::Align::TYPE::RIGHT },
|
22
|
+
text: 'h',
|
23
|
+
max_value: 3,
|
24
|
+
diff: 0,
|
25
|
+
expected_option: Kosi::Align::TYPE::RIGHT,
|
26
|
+
expected: ' h'
|
27
|
+
},
|
28
|
+
{
|
29
|
+
case_no: 3,
|
30
|
+
case_title: 'align options left case',
|
31
|
+
options: { Kosi::OptionKeys::ALIGN => Kosi::Align::TYPE::LEFT },
|
32
|
+
text: 'h',
|
33
|
+
max_value: 3,
|
34
|
+
diff: 0,
|
35
|
+
expected_option: Kosi::Align::TYPE::LEFT,
|
36
|
+
expected: 'h '
|
37
|
+
},
|
38
|
+
{
|
39
|
+
case_no: 4,
|
40
|
+
case_title: 'align options center case',
|
41
|
+
options: { Kosi::OptionKeys::ALIGN => Kosi::Align::TYPE::CENTER },
|
42
|
+
text: 'h',
|
43
|
+
max_value: 3,
|
44
|
+
diff: 0,
|
45
|
+
expected_option: Kosi::Align::TYPE::CENTER,
|
46
|
+
expected: ' h '
|
47
|
+
}
|
48
|
+
]
|
49
|
+
|
50
|
+
cases.each do |c|
|
51
|
+
it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do
|
52
|
+
begin
|
53
|
+
case_before c
|
54
|
+
|
55
|
+
# -- given --
|
56
|
+
header = Kosi::Align.new(c[:options])
|
57
|
+
|
58
|
+
# -- when --
|
59
|
+
actual = header.apply(c[:text], c[:max_value], c[:diff])
|
60
|
+
|
61
|
+
# -- then --
|
62
|
+
expect(actual).to eq(c[:expected])
|
63
|
+
ensure
|
64
|
+
case_after c
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def case_before(c)
|
69
|
+
# implement each case before
|
70
|
+
end
|
71
|
+
|
72
|
+
def case_after(c)
|
73
|
+
# implement each case after
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|