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.
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core'
3
+ require 'rspec/core/rake_task'
4
+ task default: [:spec]
5
+
6
+ RSpec::Core::RakeTask.new(:spec) do |spec|
7
+ spec.pattern = 'spec/**/*_spec.rb'
8
+ end
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,3 @@
1
+ # encoding: utf-8
2
+ require 'kosi/validators/each_array_length_validator'
3
+ require 'kosi/validators/row_type_validator'
@@ -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
@@ -0,0 +1,4 @@
1
+ # TableFormat for Terminal(Use Japanese Charactors)
2
+ module Kosi
3
+ VERSION = '0.0.1'
4
+ 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
+ # 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
@@ -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