tabulard 0.2.0
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/LICENSE +202 -0
- data/README.md +43 -0
- data/VERSION +1 -0
- data/lib/sheetah/attribute.rb +60 -0
- data/lib/sheetah/attribute_types/composite.rb +57 -0
- data/lib/sheetah/attribute_types/scalar.rb +58 -0
- data/lib/sheetah/attribute_types/value.rb +62 -0
- data/lib/sheetah/attribute_types/value.rb.orig +68 -0
- data/lib/sheetah/attribute_types.rb +49 -0
- data/lib/sheetah/backends/csv.rb +92 -0
- data/lib/sheetah/backends/wrapper.rb +57 -0
- data/lib/sheetah/backends/xlsx.rb +80 -0
- data/lib/sheetah/backends.rb +11 -0
- data/lib/sheetah/column.rb +31 -0
- data/lib/sheetah/errors/error.rb +8 -0
- data/lib/sheetah/errors/spec_error.rb +10 -0
- data/lib/sheetah/errors/type_error.rb +10 -0
- data/lib/sheetah/frozen.rb +9 -0
- data/lib/sheetah/headers.rb +96 -0
- data/lib/sheetah/messaging/config.rb +19 -0
- data/lib/sheetah/messaging/constants.rb +17 -0
- data/lib/sheetah/messaging/message.rb +70 -0
- data/lib/sheetah/messaging/message_variant.rb +47 -0
- data/lib/sheetah/messaging/messages/cleaned_string.rb +18 -0
- data/lib/sheetah/messaging/messages/duplicated_header.rb +21 -0
- data/lib/sheetah/messaging/messages/invalid_header.rb +21 -0
- data/lib/sheetah/messaging/messages/missing_column.rb +21 -0
- data/lib/sheetah/messaging/messages/must_be_array.rb +18 -0
- data/lib/sheetah/messaging/messages/must_be_boolsy.rb +21 -0
- data/lib/sheetah/messaging/messages/must_be_date.rb +21 -0
- data/lib/sheetah/messaging/messages/must_be_email.rb +21 -0
- data/lib/sheetah/messaging/messages/must_be_string.rb +18 -0
- data/lib/sheetah/messaging/messages/must_exist.rb +18 -0
- data/lib/sheetah/messaging/messages/sheet_error.rb +18 -0
- data/lib/sheetah/messaging/messenger.rb +133 -0
- data/lib/sheetah/messaging/validations/base_validator.rb +43 -0
- data/lib/sheetah/messaging/validations/dsl.rb +31 -0
- data/lib/sheetah/messaging/validations/invalid_message.rb +12 -0
- data/lib/sheetah/messaging/validations/mixins.rb +57 -0
- data/lib/sheetah/messaging/validations.rb +35 -0
- data/lib/sheetah/messaging.rb +22 -0
- data/lib/sheetah/row_processor.rb +41 -0
- data/lib/sheetah/row_processor_result.rb +20 -0
- data/lib/sheetah/row_value_builder.rb +53 -0
- data/lib/sheetah/sheet/col_converter.rb +62 -0
- data/lib/sheetah/sheet.rb +107 -0
- data/lib/sheetah/sheet_processor.rb +61 -0
- data/lib/sheetah/sheet_processor_result.rb +18 -0
- data/lib/sheetah/specification.rb +30 -0
- data/lib/sheetah/template.rb +85 -0
- data/lib/sheetah/template_config.rb +35 -0
- data/lib/sheetah/types/cast.rb +20 -0
- data/lib/sheetah/types/cast_chain.rb +49 -0
- data/lib/sheetah/types/composites/array.rb +16 -0
- data/lib/sheetah/types/composites/array_compact.rb +13 -0
- data/lib/sheetah/types/composites/composite.rb +32 -0
- data/lib/sheetah/types/container.rb +81 -0
- data/lib/sheetah/types/scalars/boolsy.rb +12 -0
- data/lib/sheetah/types/scalars/boolsy_cast.rb +35 -0
- data/lib/sheetah/types/scalars/date_string.rb +12 -0
- data/lib/sheetah/types/scalars/date_string_cast.rb +43 -0
- data/lib/sheetah/types/scalars/email.rb +12 -0
- data/lib/sheetah/types/scalars/email_cast.rb +28 -0
- data/lib/sheetah/types/scalars/scalar.rb +29 -0
- data/lib/sheetah/types/scalars/scalar_cast.rb +49 -0
- data/lib/sheetah/types/scalars/string.rb +18 -0
- data/lib/sheetah/types/type.rb +103 -0
- data/lib/sheetah/utils/cell_string_cleaner.rb +29 -0
- data/lib/sheetah/utils/monadic_result.rb +174 -0
- data/lib/sheetah.rb +31 -0
- metadata +118 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../message_variant"
|
4
|
+
|
5
|
+
module Sheetah
|
6
|
+
module Messaging
|
7
|
+
module Messages
|
8
|
+
class MustBeDate < MessageVariant
|
9
|
+
CODE = "must_be_date"
|
10
|
+
|
11
|
+
def_validator do
|
12
|
+
cell
|
13
|
+
|
14
|
+
def validate_code_data(message)
|
15
|
+
message.code_data in { format: String }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../message_variant"
|
4
|
+
|
5
|
+
module Sheetah
|
6
|
+
module Messaging
|
7
|
+
module Messages
|
8
|
+
class MustBeEmail < MessageVariant
|
9
|
+
CODE = "must_be_email"
|
10
|
+
|
11
|
+
def_validator do
|
12
|
+
cell
|
13
|
+
|
14
|
+
def validate_code_data(message)
|
15
|
+
message.code_data in { value: String }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../message_variant"
|
4
|
+
|
5
|
+
module Sheetah
|
6
|
+
module Messaging
|
7
|
+
module Messages
|
8
|
+
class MustBeString < MessageVariant
|
9
|
+
CODE = "must_be_string"
|
10
|
+
|
11
|
+
def_validator do
|
12
|
+
cell
|
13
|
+
nil_code_data
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../message_variant"
|
4
|
+
|
5
|
+
module Sheetah
|
6
|
+
module Messaging
|
7
|
+
module Messages
|
8
|
+
class MustExist < MessageVariant
|
9
|
+
CODE = "must_exist"
|
10
|
+
|
11
|
+
def_validator do
|
12
|
+
cell
|
13
|
+
nil_code_data
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../message_variant"
|
4
|
+
|
5
|
+
module Sheetah
|
6
|
+
module Messaging
|
7
|
+
module Messages
|
8
|
+
class SheetError < MessageVariant
|
9
|
+
CODE = "sheet_error"
|
10
|
+
|
11
|
+
def_validator do
|
12
|
+
sheet
|
13
|
+
nil_code_data
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "constants"
|
4
|
+
|
5
|
+
module Sheetah
|
6
|
+
module Messaging
|
7
|
+
class Messenger
|
8
|
+
def initialize(
|
9
|
+
scope: SCOPES::SHEET,
|
10
|
+
scope_data: nil,
|
11
|
+
validate_messages: Messaging.config.validate_messages
|
12
|
+
)
|
13
|
+
@scope = scope.freeze
|
14
|
+
@scope_data = scope_data.freeze
|
15
|
+
@messages = []
|
16
|
+
@validate_messages = validate_messages
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :scope, :scope_data, :messages, :validate_messages
|
20
|
+
|
21
|
+
def ==(other)
|
22
|
+
other.is_a?(self.class) &&
|
23
|
+
scope == other.scope &&
|
24
|
+
scope_data == other.scope_data &&
|
25
|
+
messages == other.messages &&
|
26
|
+
validate_messages == other.validate_messages
|
27
|
+
end
|
28
|
+
|
29
|
+
def dup
|
30
|
+
self.class.new(
|
31
|
+
scope: @scope,
|
32
|
+
scope_data: @scope_data,
|
33
|
+
validate_messages: @validate_messages
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def scoping!(scope, scope_data, &block)
|
38
|
+
scope = scope.freeze
|
39
|
+
scope_data = scope_data.freeze
|
40
|
+
|
41
|
+
if block
|
42
|
+
replace_scoping_block(scope, scope_data, &block)
|
43
|
+
else
|
44
|
+
replace_scoping_noblock(scope, scope_data)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def scoping(...)
|
49
|
+
dup.scoping!(...)
|
50
|
+
end
|
51
|
+
|
52
|
+
def scope_row!(row, &block)
|
53
|
+
scope = case @scope
|
54
|
+
when SCOPES::COL, SCOPES::CELL
|
55
|
+
SCOPES::CELL
|
56
|
+
else
|
57
|
+
SCOPES::ROW
|
58
|
+
end
|
59
|
+
|
60
|
+
scope_data = @scope_data.dup || {}
|
61
|
+
scope_data[:row] = row
|
62
|
+
|
63
|
+
scoping!(scope, scope_data, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
def scope_col!(col, &block)
|
67
|
+
scope = case @scope
|
68
|
+
when SCOPES::ROW, SCOPES::CELL
|
69
|
+
SCOPES::CELL
|
70
|
+
else
|
71
|
+
SCOPES::COL
|
72
|
+
end
|
73
|
+
|
74
|
+
scope_data = @scope_data.dup || {}
|
75
|
+
scope_data[:col] = col
|
76
|
+
|
77
|
+
scoping!(scope, scope_data, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
def scope_row(...)
|
81
|
+
dup.scope_row!(...)
|
82
|
+
end
|
83
|
+
|
84
|
+
def scope_col(...)
|
85
|
+
dup.scope_col!(...)
|
86
|
+
end
|
87
|
+
|
88
|
+
def warn(message)
|
89
|
+
add(message, severity: SEVERITIES::WARN)
|
90
|
+
end
|
91
|
+
|
92
|
+
def error(message)
|
93
|
+
add(message, severity: SEVERITIES::ERROR)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def add(message, severity:)
|
99
|
+
message.scope = @scope
|
100
|
+
message.scope_data = @scope_data
|
101
|
+
message.severity = severity
|
102
|
+
|
103
|
+
message.validate if @validate_messages
|
104
|
+
|
105
|
+
messages << message
|
106
|
+
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
def replace_scoping_noblock(new_scope, new_scope_data)
|
111
|
+
@scope = new_scope
|
112
|
+
@scope_data = new_scope_data
|
113
|
+
|
114
|
+
self
|
115
|
+
end
|
116
|
+
|
117
|
+
def replace_scoping_block(new_scope, new_scope_data)
|
118
|
+
prev_scope = @scope
|
119
|
+
prev_scope_data = @scope_data
|
120
|
+
|
121
|
+
@scope = new_scope
|
122
|
+
@scope_data = new_scope_data
|
123
|
+
|
124
|
+
begin
|
125
|
+
yield self
|
126
|
+
ensure
|
127
|
+
@scope = prev_scope
|
128
|
+
@scope_data = prev_scope_data
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "dsl"
|
4
|
+
require_relative "invalid_message"
|
5
|
+
|
6
|
+
module Sheetah
|
7
|
+
module Messaging
|
8
|
+
module Validations
|
9
|
+
class BaseValidator
|
10
|
+
extend DSL
|
11
|
+
|
12
|
+
def validate(message)
|
13
|
+
errors = []
|
14
|
+
|
15
|
+
errors << "code" unless validate_code(message)
|
16
|
+
errors << "code_data" unless validate_code_data(message)
|
17
|
+
errors << "scope" unless validate_scope(message)
|
18
|
+
errors << "scope_data" unless validate_scope_data(message)
|
19
|
+
|
20
|
+
return if errors.empty?
|
21
|
+
|
22
|
+
raise InvalidMessage, "#{errors.join(", ")} <#{message.class}>#{message.to_h}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate_code(_message)
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate_code_data(_message)
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_scope(_message)
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_scope_data(_message)
|
38
|
+
true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "mixins"
|
4
|
+
|
5
|
+
module Sheetah
|
6
|
+
module Messaging
|
7
|
+
module Validations
|
8
|
+
module DSL
|
9
|
+
def cell
|
10
|
+
include Mixins::CellValidations
|
11
|
+
end
|
12
|
+
|
13
|
+
def col
|
14
|
+
include Mixins::ColValidations
|
15
|
+
end
|
16
|
+
|
17
|
+
def row
|
18
|
+
include Mixins::RowValidations
|
19
|
+
end
|
20
|
+
|
21
|
+
def sheet
|
22
|
+
include Mixins::SheetValidations
|
23
|
+
end
|
24
|
+
|
25
|
+
def nil_code_data
|
26
|
+
include Mixins::NilCodeData
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../constants"
|
4
|
+
|
5
|
+
module Sheetah
|
6
|
+
module Messaging
|
7
|
+
module Validations
|
8
|
+
module Mixins
|
9
|
+
module CellValidations
|
10
|
+
def validate_scope(message)
|
11
|
+
message.scope == SCOPES::CELL
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate_scope_data(message)
|
15
|
+
message.scope_data in { col: String, row: Integer }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ColValidations
|
20
|
+
def validate_scope(message)
|
21
|
+
message.scope == SCOPES::COL
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_scope_data(message)
|
25
|
+
message.scope_data in { col: String }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module RowValidations
|
30
|
+
def validate_scope(message)
|
31
|
+
message.scope == SCOPES::ROW
|
32
|
+
end
|
33
|
+
|
34
|
+
def validate_scope_data(message)
|
35
|
+
message.scope_data in { row: Integer }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module SheetValidations
|
40
|
+
def validate_scope(message)
|
41
|
+
message.scope == SCOPES::SHEET
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate_scope_data(message)
|
45
|
+
message.scope_data.nil?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module NilCodeData
|
50
|
+
def validate_code_data(message)
|
51
|
+
message.code_data.nil?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "validations/base_validator"
|
4
|
+
|
5
|
+
module Sheetah
|
6
|
+
module Messaging
|
7
|
+
module Validations
|
8
|
+
module ClassMethods
|
9
|
+
def def_validator(base: validator&.class || BaseValidator, &block)
|
10
|
+
@validator = Class.new(base, &block).new.freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
def validator
|
14
|
+
if defined?(@validator)
|
15
|
+
@validator
|
16
|
+
elsif superclass.respond_to?(:validator)
|
17
|
+
superclass.validator
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate(message)
|
22
|
+
validator&.validate(message)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.included(message_class)
|
27
|
+
message_class.extend(ClassMethods)
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate
|
31
|
+
self.class.validate(self)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sheetah
|
4
|
+
module Messaging
|
5
|
+
require_relative "messaging/config"
|
6
|
+
require_relative "messaging/constants"
|
7
|
+
require_relative "messaging/message"
|
8
|
+
require_relative "messaging/messenger"
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :config
|
12
|
+
|
13
|
+
def configure
|
14
|
+
config = self.config.dup
|
15
|
+
yield config
|
16
|
+
self.config = config.freeze
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
self.config = Config.new.freeze
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "row_processor_result"
|
4
|
+
require_relative "row_value_builder"
|
5
|
+
|
6
|
+
module Sheetah
|
7
|
+
class RowProcessor
|
8
|
+
def initialize(headers:, messenger:)
|
9
|
+
@headers = headers
|
10
|
+
@messenger = messenger
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(row)
|
14
|
+
messenger = @messenger.dup
|
15
|
+
|
16
|
+
builder = RowValueBuilder.new(messenger)
|
17
|
+
|
18
|
+
messenger.scope_row!(row.row) do
|
19
|
+
@headers.each do |header|
|
20
|
+
cell = row.value[header.row_value_index]
|
21
|
+
|
22
|
+
messenger.scope_col!(cell.col) do
|
23
|
+
builder.add(header.column, cell.value)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
build_result(row, builder, messenger)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def build_result(row, builder, messenger)
|
34
|
+
RowProcessorResult.new(
|
35
|
+
row: row.row,
|
36
|
+
result: builder.result,
|
37
|
+
messages: messenger.messages
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sheetah
|
4
|
+
class RowProcessorResult
|
5
|
+
def initialize(row:, result:, messages: [])
|
6
|
+
@row = row
|
7
|
+
@result = result
|
8
|
+
@messages = messages
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :row, :result, :messages
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
other.is_a?(self.class) &&
|
15
|
+
row == other.row &&
|
16
|
+
result == other.result &&
|
17
|
+
messages == other.messages
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require_relative "utils/monadic_result"
|
5
|
+
|
6
|
+
module Sheetah
|
7
|
+
class RowValueBuilder
|
8
|
+
include Utils::MonadicResult
|
9
|
+
|
10
|
+
def initialize(messenger)
|
11
|
+
@messenger = messenger
|
12
|
+
@data = {}
|
13
|
+
@composites = Set.new
|
14
|
+
@failure = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(column, value)
|
18
|
+
key = column.key
|
19
|
+
type = column.type
|
20
|
+
index = column.index
|
21
|
+
|
22
|
+
result = type.scalar(index, value, @messenger)
|
23
|
+
|
24
|
+
result.bind do |scalar|
|
25
|
+
if type.composite?
|
26
|
+
@composites << [key, type]
|
27
|
+
@data[key] ||= []
|
28
|
+
@data[key][index] = scalar
|
29
|
+
else
|
30
|
+
@data[key] = scalar
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
result.or { @failure = true }
|
35
|
+
|
36
|
+
result
|
37
|
+
end
|
38
|
+
|
39
|
+
def result
|
40
|
+
return Failure() if @failure
|
41
|
+
|
42
|
+
Do() do
|
43
|
+
@composites.each do |key, type|
|
44
|
+
value = type.composite(@data[key], @messenger).unwrap
|
45
|
+
|
46
|
+
@data[key] = value
|
47
|
+
end
|
48
|
+
|
49
|
+
Success(@data)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sheetah
|
4
|
+
module Sheet
|
5
|
+
class ColConverter
|
6
|
+
CHARSET = ("A".."Z").to_a.freeze
|
7
|
+
CHARSET_SIZE = CHARSET.size
|
8
|
+
CHAR_TO_INT = CHARSET.map.with_index(1).to_h.freeze
|
9
|
+
INT_TO_CHAR = CHAR_TO_INT.invert.freeze
|
10
|
+
|
11
|
+
def col2int(col)
|
12
|
+
raise ArgumentError unless col.is_a?(String) && !col.empty?
|
13
|
+
|
14
|
+
int = 0
|
15
|
+
|
16
|
+
col.each_char.reverse_each.with_index do |char, pow|
|
17
|
+
int += char2int(char) * (CHARSET_SIZE**pow)
|
18
|
+
end
|
19
|
+
|
20
|
+
int
|
21
|
+
end
|
22
|
+
|
23
|
+
def int2col(int)
|
24
|
+
raise ArgumentError unless int.is_a?(Integer) && int.positive?
|
25
|
+
|
26
|
+
x = int
|
27
|
+
y = CHARSET_SIZE
|
28
|
+
col = +""
|
29
|
+
|
30
|
+
until x.zero?
|
31
|
+
q, r = x.divmod(y)
|
32
|
+
|
33
|
+
if r.zero?
|
34
|
+
q -= 1
|
35
|
+
r = y
|
36
|
+
end
|
37
|
+
|
38
|
+
x = q
|
39
|
+
|
40
|
+
col << int2char(r)
|
41
|
+
end
|
42
|
+
|
43
|
+
col.reverse!
|
44
|
+
col.freeze
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def char2int(char)
|
50
|
+
CHAR_TO_INT[char] || raise(ArgumentError, char.inspect)
|
51
|
+
end
|
52
|
+
|
53
|
+
def int2char(int)
|
54
|
+
INT_TO_CHAR[int] || raise(ArgumentError, int.inspect)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private_constant :ColConverter
|
59
|
+
|
60
|
+
COL_CONVERTER = ColConverter.new.freeze
|
61
|
+
end
|
62
|
+
end
|