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