kosi 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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