csv_plus_plus 0.0.5 → 0.1.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +1 -0
  4. data/lib/csv_plus_plus/cell.rb +24 -8
  5. data/lib/csv_plus_plus/cli.rb +29 -16
  6. data/lib/csv_plus_plus/cli_flag.rb +10 -2
  7. data/lib/csv_plus_plus/code_section.rb +22 -3
  8. data/lib/csv_plus_plus/color.rb +19 -5
  9. data/lib/csv_plus_plus/google_options.rb +6 -2
  10. data/lib/csv_plus_plus/graph.rb +0 -1
  11. data/lib/csv_plus_plus/language/ast_builder.rb +68 -0
  12. data/lib/csv_plus_plus/language/benchmarked_compiler.rb +65 -0
  13. data/lib/csv_plus_plus/language/builtins.rb +46 -0
  14. data/lib/csv_plus_plus/language/cell_value.tab.rb +1 -2
  15. data/lib/csv_plus_plus/language/code_section.tab.rb +1 -2
  16. data/lib/csv_plus_plus/language/compiler.rb +74 -86
  17. data/lib/csv_plus_plus/language/entities/boolean.rb +3 -2
  18. data/lib/csv_plus_plus/language/entities/cell_reference.rb +10 -3
  19. data/lib/csv_plus_plus/language/entities/entity.rb +20 -8
  20. data/lib/csv_plus_plus/language/entities/function.rb +6 -4
  21. data/lib/csv_plus_plus/language/entities/function_call.rb +4 -3
  22. data/lib/csv_plus_plus/language/entities/number.rb +6 -4
  23. data/lib/csv_plus_plus/language/entities/runtime_value.rb +9 -8
  24. data/lib/csv_plus_plus/language/entities/string.rb +6 -4
  25. data/lib/csv_plus_plus/language/references.rb +22 -5
  26. data/lib/csv_plus_plus/language/runtime.rb +80 -22
  27. data/lib/csv_plus_plus/language/scope.rb +29 -38
  28. data/lib/csv_plus_plus/language/syntax_error.rb +10 -5
  29. data/lib/csv_plus_plus/lexer/lexer.rb +25 -12
  30. data/lib/csv_plus_plus/lexer/tokenizer.rb +35 -11
  31. data/lib/csv_plus_plus/modifier.rb +38 -13
  32. data/lib/csv_plus_plus/modifier.tab.rb +2 -2
  33. data/lib/csv_plus_plus/options.rb +17 -2
  34. data/lib/csv_plus_plus/row.rb +15 -4
  35. data/lib/csv_plus_plus/template.rb +10 -6
  36. data/lib/csv_plus_plus/version.rb +1 -1
  37. data/lib/csv_plus_plus/writer/excel.rb +2 -9
  38. data/lib/csv_plus_plus/writer/file_backer_upper.rb +22 -20
  39. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +8 -10
  40. data/lib/csv_plus_plus/writer/google_sheets.rb +4 -10
  41. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +23 -15
  42. data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +15 -8
  43. data/lib/csv_plus_plus.rb +21 -3
  44. metadata +5 -2
@@ -4,31 +4,44 @@ require_relative 'entities'
4
4
  require_relative 'syntax_error'
5
5
  require 'tempfile'
6
6
 
7
- ENTITIES = ::CSVPlusPlus::Language::Entities
8
-
9
- RUNTIME_VARIABLES = {
10
- rownum: ::ENTITIES::RuntimeValue.new(->(r) { ::ENTITIES::Number.new(r.row_index + 1) }),
11
- cellnum: ::ENTITIES::RuntimeValue.new(->(r) { ::ENTITIES::Number.new(r.cell_index + 1) })
12
- }.freeze
13
-
14
7
  module CSVPlusPlus
15
8
  module Language
16
- ##
17
- # The runtime state of the compiler (the current linenumber/row, cell, etc)
9
+ # The runtime state of the compiler (the current +line_number+/+row_index+, +cell+ being processed, etc). We take
10
+ # multiple runs through the input file for parsing so it's really convenient to have a central place for these
11
+ # things to be managed.
12
+ #
13
+ # @attr_reader filename [String, nil] The filename that the input came from (mostly used for debugging since
14
+ # +filename+ can be +nil+ if it's read from stdin.
15
+ # @attr_reader length_of_code_section [Integer] The length (count of lines) of the code section part of the original
16
+ # input.
17
+ # @attr_reader length_of_csv_section [Integer] The length (count of lines) of the CSV part of the original csvpp
18
+ # input.
19
+ # @attr_reader length_of_original_file [Integer] The length (count of lines) of the original csvpp input.
20
+ #
21
+ # @attr cell [Cell] The current cell being processed
22
+ # @attr cell_index [Integer] The index of the current cell being processed (starts at 0)
23
+ # @attr row_index [Integer] The index of the current row being processed (starts at 0)
24
+ # @attr line_number [Integer] The line number of the original csvpp template (starts at 1)
18
25
  class Runtime
19
26
  attr_reader :filename, :length_of_code_section, :length_of_csv_section, :length_of_original_file
20
27
 
21
28
  attr_accessor :cell, :cell_index, :row_index, :line_number
22
29
 
23
- # initialize
30
+ # @param input [String] The input to be parsed
31
+ # @param filename [String, nil] The filename that the input came from (mostly used for debugging since +filename+
32
+ # can be +nil+ if it's read from stdin
24
33
  def initialize(input:, filename:)
25
34
  @filename = filename || 'stdin'
26
35
 
27
36
  init_input!(input)
28
- init!(1)
37
+ start!
29
38
  end
30
39
 
31
- # map over an unparsed file and keep track of line_number and row_index
40
+ # Map over an a csvpp file and keep track of line_number and row_index
41
+ #
42
+ # @param lines [Array]
43
+ #
44
+ # @return [Array]
32
45
  def map_lines(lines, &block)
33
46
  @line_number = 1
34
47
  lines.map do |line|
@@ -36,7 +49,11 @@ module CSVPlusPlus
36
49
  end
37
50
  end
38
51
 
39
- # map over a single row and keep track of the cell and it's index
52
+ # Map over a single row and keep track of the cell and it's index
53
+ #
54
+ # @param row [Array<Cell>] The row to map each cell over
55
+ #
56
+ # @return [Array]
40
57
  def map_row(row, &block)
41
58
  @cell_index = 0
42
59
  row.map.with_index do |cell, index|
@@ -45,7 +62,12 @@ module CSVPlusPlus
45
62
  end
46
63
  end
47
64
 
48
- # map over all rows and keep track of row and line numbers
65
+ # Map over all rows and keep track of row and line numbers
66
+ #
67
+ # @param rows [Array<Row>] The rows to map over (and keep track of indexes)
68
+ # @param cells_too [boolean] If the cells of each +row+ should be iterated over also.
69
+ #
70
+ # @return [Array]
49
71
  def map_rows(rows, cells_too: false, &block)
50
72
  @row_index = 0
51
73
  map_lines(rows) do |row|
@@ -59,56 +81,92 @@ module CSVPlusPlus
59
81
  end
60
82
 
61
83
  # Increment state to the next line
84
+ #
85
+ # @return [Integer]
62
86
  def next_line!
63
87
  @row_index += 1 unless @row_index.nil?
64
88
  @line_number += 1
65
89
  end
66
90
 
91
+ # Return the current spreadsheet row number. It parallels +@row_index+ but starts at 1.
92
+ #
93
+ # @return [Integer, nil]
94
+ def rownum
95
+ return if @row_index.nil?
96
+
97
+ @row_index + 1
98
+ end
99
+
67
100
  # Set the current cell and index
101
+ #
102
+ # @param cell [Cell] The current cell
103
+ # @param cell_index [Integer] The index of the cell
68
104
  def set_cell!(cell, cell_index)
69
105
  @cell = cell
70
106
  @cell_index = cell_index
71
107
  end
72
108
 
73
- # Each time we run a parse on the input, call this so that the runtime state
74
- # is set to it's default values
75
- def init!(start_line_number_at)
109
+ # Each time we run a parse on the input, reset the runtime state starting at the beginning of the file
110
+ def start!
76
111
  @row_index = @cell_index = nil
77
- @line_number = start_line_number_at
112
+ @line_number = 1
113
+ end
114
+
115
+ # Reset the runtime state starting at the CSV section
116
+ def start_at_csv!
117
+ # TODO: isn't the input re-written anyway without the code section? why do we need this?
118
+ start!
119
+ @line_number = @length_of_code_section || 1
78
120
  end
79
121
 
80
- # to_s
122
+ # @return [String]
81
123
  def to_s
82
124
  "Runtime(cell: #{@cell}, row_index: #{@row_index}, cell_index: #{@cell_index})"
83
125
  end
84
126
 
85
- # get the current (entity) value of a runtime value
127
+ # Get the current (entity) value of a runtime value
128
+ #
129
+ # @param var_id [String, Symbol] The Variable#id of the variable being resolved.
130
+ #
131
+ # @return [Entity]
86
132
  def runtime_value(var_id)
87
133
  if runtime_variable?(var_id)
88
- ::RUNTIME_VARIABLES[var_id.to_sym].resolve_fn.call(self)
134
+ ::CSVPlusPlus::Language::Builtins::VARIABLES[var_id.to_sym].resolve_fn.call(self)
89
135
  else
90
136
  raise_syntax_error('Undefined variable', var_id)
91
137
  end
92
138
  end
93
139
 
94
140
  # Is +var_id+ a runtime variable? (it's a static variable otherwise)
141
+ #
142
+ # @param var_id [String, Symbol] The Variable#id to check if it's a runtime variable
143
+ #
144
+ # @return [boolean]
95
145
  def runtime_variable?(var_id)
96
- ::RUNTIME_VARIABLES.key?(var_id.to_sym)
146
+ ::CSVPlusPlus::Language::Builtins::VARIABLES.key?(var_id.to_sym)
97
147
  end
98
148
 
99
149
  # Called when an error is encoutered during parsing. It will construct a useful
100
150
  # error with the current +@row/@cell_index+, +@line_number+ and +@filename+
151
+ #
152
+ # @param message [String] A message relevant to why this error is being raised.
153
+ # @param bad_input [String] The offending input that caused this error to be thrown.
154
+ # @param wrapped_error [StandardError, nil] The underlying error that was raised (if it's not from our own logic)
101
155
  def raise_syntax_error(message, bad_input, wrapped_error: nil)
102
156
  raise(::CSVPlusPlus::Language::SyntaxError.new(message, bad_input, self, wrapped_error:))
103
157
  end
104
158
 
105
159
  # The currently available input for parsing. The tmp state will be re-written
106
160
  # between parsing the code section and the CSV section
161
+ #
162
+ # @return [String]
107
163
  def input
108
164
  @tmp
109
165
  end
110
166
 
111
167
  # We mutate the input over and over. It's ok because it's just a Tempfile
168
+ #
169
+ # @param data [String] The data to rewrite our input file to
112
170
  def rewrite_input!(data)
113
171
  @tmp.truncate(0)
114
172
  @tmp.write(data)
@@ -6,40 +6,29 @@ require_relative './entities'
6
6
  require_relative './references'
7
7
  require_relative './syntax_error'
8
8
 
9
- BUILTIN_FUNCTIONS = {
10
- # =CELLREF(C) === =INDIRECT(CONCAT($$C, $$rownum))
11
- cellref: ::CSVPlusPlus::Language::Entities::Function.new(
12
- :cellref,
13
- [:cell],
14
- ::CSVPlusPlus::Language::Entities::FunctionCall.new(
15
- :indirect,
16
- [
17
- ::CSVPlusPlus::Language::Entities::FunctionCall.new(
18
- :concat,
19
- [
20
- ::CSVPlusPlus::Language::Entities::Variable.new(:cell),
21
- ::CSVPlusPlus::Language::Entities::Variable.new(:rownum)
22
- ]
23
- )
24
- ]
25
- )
26
- )
27
- }.freeze
28
-
29
9
  module CSVPlusPlus
30
10
  module Language
31
11
  # A class representing the scope of the current Template and responsible for resolving variables
12
+ #
13
+ # @attr_reader code_section [CodeSection] The CodeSection containing variables and functions to be resolved
14
+ # @attr_reader runtime [Runtime] The compiler's current runtime
15
+ #
32
16
  # rubocop:disable Metrics/ClassLength
33
17
  class Scope
34
18
  attr_reader :code_section, :runtime
35
19
 
36
20
  # initialize with a +Runtime+ and optional +CodeSection+
21
+ #
22
+ # @param runtime [Runtime]
23
+ # @param code_section [Runtime, nil]
37
24
  def initialize(runtime:, code_section: nil)
38
25
  @code_section = code_section if code_section
39
26
  @runtime = runtime
40
27
  end
41
28
 
42
29
  # Resolve all values in the ast of the current cell being processed
30
+ #
31
+ # @return [Entity]
43
32
  def resolve_cell_value
44
33
  return unless (ast = @runtime.cell&.ast)
45
34
 
@@ -56,14 +45,14 @@ module CSVPlusPlus
56
45
  end
57
46
 
58
47
  # Set the +code_section+ and resolve all inner dependencies in it's variables and functions.
48
+ #
49
+ # @param code_section [CodeSection] The code_section to be resolved
59
50
  def code_section=(code_section)
60
51
  @code_section = code_section
61
-
62
52
  resolve_static_variables!
63
- resolve_static_functions!
64
53
  end
65
54
 
66
- # to_s
55
+ # @return [String]
67
56
  def to_s
68
57
  "Scope(code_section: #{@code_section}, runtime: #{@runtime})"
69
58
  end
@@ -71,10 +60,10 @@ module CSVPlusPlus
71
60
  private
72
61
 
73
62
  # Resolve all variable references defined statically in the code section
63
+ # TODO: experiment with getting rid of this - does it even play correctly with runtime vars?
74
64
  def resolve_static_variables!
75
65
  variables = @code_section.variables
76
66
  last_var_dependencies = {}
77
- # TODO: might not need the infinite loop wrap
78
67
  loop do
79
68
  var_dependencies, resolution_order = variable_resolution_order(only_static_vars(variables))
80
69
  return if var_dependencies == last_var_dependencies
@@ -89,14 +78,6 @@ module CSVPlusPlus
89
78
  var_dependencies.reject { |k| @runtime.runtime_variable?(k) }
90
79
  end
91
80
 
92
- # Resolve all functions defined statically in the code section
93
- def resolve_static_functions!
94
- # TODO: I'm still torn if it's worth replacing function references
95
- #
96
- # my current theory is that if we resolve static functions befor processing each cell,
97
- # overall compile time will be improved because there will be less to do for each cell
98
- end
99
-
100
81
  def resolve_functions(ast, refs)
101
82
  refs.reduce(ast.dup) do |acc, elem|
102
83
  function_replace(acc, elem.id, resolve_function(elem.id))
@@ -112,10 +93,13 @@ module CSVPlusPlus
112
93
  # Make a copy of the AST represented by +node+ and replace +fn_id+ with +replacement+ throughout
113
94
  def function_replace(node, fn_id, replacement)
114
95
  if node.function_call? && node.id == fn_id
115
- apply_arguments(replacement, node)
96
+ call_function_or_runtime_value(replacement, node)
116
97
  elsif node.function_call?
117
- arguments = node.arguments.map { |n| function_replace(n, fn_id, replacement) }
118
- ::CSVPlusPlus::Language::Entities::FunctionCall.new(node.id, arguments)
98
+ # not our function, but continue our depth first search on it
99
+ ::CSVPlusPlus::Language::Entities::FunctionCall.new(
100
+ node.id,
101
+ node.arguments.map { |n| function_replace(n, fn_id, replacement) }
102
+ )
119
103
  else
120
104
  node
121
105
  end
@@ -125,11 +109,18 @@ module CSVPlusPlus
125
109
  id = fn_id.to_sym
126
110
  return @code_section.functions[id] if @code_section.defined_function?(id)
127
111
 
128
- # this will throw a syntax error if it doesn't exist (which is what we want)
129
- return ::BUILTIN_FUNCTIONS[id] if ::BUILTIN_FUNCTIONS.key?(id)
112
+ ::CSVPlusPlus::Language::Builtins::FUNCTIONS[id]
113
+ end
114
+
115
+ def call_function_or_runtime_value(function_or_runtime_value, function_call)
116
+ if function_or_runtime_value.function?
117
+ call_function(function_or_runtime_value, function_call)
118
+ else
119
+ function_or_runtime_value.resolve_fn.call(@runtime, function_call.arguments)
120
+ end
130
121
  end
131
122
 
132
- def apply_arguments(function, function_call)
123
+ def call_function(function, function_call)
133
124
  i = 0
134
125
  function.arguments.reduce(function.body.dup) do |ast, argument|
135
126
  variable_replace(ast, argument, function_call.arguments[i]).tap do
@@ -2,10 +2,13 @@
2
2
 
3
3
  module CSVPlusPlus
4
4
  module Language
5
- ##
6
5
  # An error that can be thrown for various syntax errors
7
6
  class SyntaxError < ::CSVPlusPlus::Error
8
- # initialize
7
+ # @param message [String] The primary message to be shown to the user
8
+ # @param bad_input [String] The offending input that caused the error to be thrown
9
+ # @param runtime [Runtime] The current runtime
10
+ # @param wrapped_error [StandardError] The underlying error that caused the syntax error. For example a
11
+ # Racc::ParseError that was thrown
9
12
  def initialize(message, bad_input, runtime, wrapped_error: nil)
10
13
  @bad_input = bad_input.to_s
11
14
  @runtime = runtime
@@ -15,19 +18,21 @@ module CSVPlusPlus
15
18
  super(message)
16
19
  end
17
20
 
18
- # to_s
21
+ # @return [String]
19
22
  def to_s
20
23
  to_trace
21
24
  end
22
25
 
23
26
  # Output a verbose user-helpful string that references the current runtime
24
27
  def to_verbose_trace
25
- warn(@wrapped_error.full_message)
26
- warn(@wrapped_error.backtrace)
28
+ warn(@wrapped_error.full_message) if @wrapped_error
29
+ warn(@wrapped_error.backtrace) if @wrapped_error
27
30
  to_trace
28
31
  end
29
32
 
30
33
  # Output a user-helpful string that references the runtime state
34
+ #
35
+ # @return [String]
31
36
  def to_trace
32
37
  "#{message_prefix}#{cell_index} #{message_postfix}"
33
38
  end
@@ -1,19 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CSVPlusPlus
4
- # Common methods to be mixed into our Racc parsers
4
+ # Common methods to be mixed into the Racc parsers
5
+ #
6
+ # @attr_reader tokens [Array]
5
7
  module Lexer
6
- # initialize
7
- def initialize
8
- @tokens = []
8
+ attr_reader :tokens
9
+
10
+ # Initialize a lexer instance with an empty +@tokens+
11
+ def initialize(tokens: [])
12
+ @tokens = tokens
9
13
  end
10
14
 
11
15
  # Used by racc to iterate each token
16
+ #
17
+ # @return [Array<(String, String)>]
12
18
  def next_token
13
19
  @tokens.shift
14
20
  end
15
21
 
16
- # parse
22
+ # Orchestate the tokenizing, parsing and error handling of parsing input. Each instance will implement their own
23
+ # #tokenizer method
24
+ #
25
+ # @return [Lexer#return_value] Each instance will define it's own +return_value+ with the result of parsing
17
26
  def parse(input, runtime)
18
27
  return if input.nil?
19
28
 
@@ -28,10 +37,20 @@ module CSVPlusPlus
28
37
 
29
38
  protected
30
39
 
40
+ # Given a +type+, instantiate the proper instance with the given +entity_args+
41
+ #
42
+ # @param type [Symbol]
43
+ # @param entity_args
44
+ def e(type, *entity_args)
45
+ ::CSVPlusPlus::Language::TYPES[type].new(*entity_args)
46
+ end
47
+
48
+ private
49
+
31
50
  def tokenize(input, runtime)
32
51
  return if input.nil?
33
52
 
34
- t = tokenizer(input)
53
+ t = tokenizer.scan(input)
35
54
 
36
55
  until t.scanner.empty?
37
56
  next if t.matches_ignore?
@@ -45,12 +64,6 @@ module CSVPlusPlus
45
64
  @tokens << %i[EOL EOL]
46
65
  end
47
66
 
48
- def e(type, *entity_args)
49
- ::CSVPlusPlus::Language::TYPES[type].new(*entity_args)
50
- end
51
-
52
- private
53
-
54
67
  def consume_token(tokenizer, runtime)
55
68
  if tokenizer.last_token
56
69
  @tokens << [tokenizer.last_token, tokenizer.last_match]
@@ -5,13 +5,14 @@ require 'strscan'
5
5
  module CSVPlusPlus
6
6
  module Lexer
7
7
  # A class that contains the use-case-specific regexes for parsing
8
+ #
9
+ # @attr_reader last_token [String] The last token that's been matched.
10
+ # @attr_reader scanner [StringScanner] The StringScanner instance that's parsing the input.
8
11
  class Tokenizer
9
12
  attr_reader :last_token, :scanner
10
13
 
11
- # initialize
12
- # rubocop:disable Metrics/ParameterLists
13
- def initialize(input:, tokens:, catchall: nil, ignore: nil, alter_matches: {}, stop_fn: nil)
14
- @scanner = ::StringScanner.new(input.strip)
14
+ # @param input [String]
15
+ def initialize(tokens:, catchall: nil, ignore: nil, alter_matches: {}, stop_fn: nil)
15
16
  @last_token = nil
16
17
 
17
18
  @catchall = catchall
@@ -20,43 +21,66 @@ module CSVPlusPlus
20
21
  @stop_fn = stop_fn
21
22
  @alter_matches = alter_matches
22
23
  end
23
- # rubocop:enable Metrics/ParameterLists
24
24
 
25
- # Scan tokens and see if any match
25
+ # Initializers a scanner for the given input to be parsed
26
+ #
27
+ # @param input The input to be tokenized
28
+ # @return [Tokenizer]
29
+ def scan(input)
30
+ @scanner = ::StringScanner.new(input.strip)
31
+ self
32
+ end
33
+
34
+ # Scan tokens and set +@last_token+ if any match
35
+ #
36
+ # @return [String, nil]
26
37
  def scan_tokens!
27
38
  m = @tokens.find { |t| @scanner.scan(t.first) }
28
39
  @last_token = m ? m[1] : nil
29
40
  end
30
41
 
31
42
  # Scan input against the catchall pattern
43
+ #
44
+ # @return [String, nil]
32
45
  def scan_catchall
33
46
  @scanner.scan(@catchall) if @catchall
34
47
  end
35
48
 
36
49
  # Scan input against the ignore pattern
50
+ #
51
+ # @return [boolean]
37
52
  def matches_ignore?
38
53
  @scanner.scan(@ignore) if @ignore
39
54
  end
40
55
 
41
56
  # The value of the last token matched
57
+ #
58
+ # @return [String, nil]
42
59
  def last_match
43
60
  return @alter_matches[@last_token].call(@scanner.matched) if @alter_matches.key?(@last_token)
44
61
 
45
62
  @scanner.matched
46
63
  end
47
64
 
48
- # Peek the input
49
- def peek
50
- @scanner.peek(100)
65
+ # Read the input but don't consume it
66
+ #
67
+ # @param peek_characters [Integer]
68
+ #
69
+ # @return [String]
70
+ def peek(peek_characters: 100)
71
+ @scanner.peek(peek_characters)
51
72
  end
52
73
 
53
74
  # Scan for our stop token (if there is one - some parsers stop early and some don't)
75
+ #
76
+ # @return [boolean]
54
77
  def stop?
55
78
  @stop_fn ? @stop_fn.call(@scanner) : false
56
79
  end
57
80
 
58
- # The rest of the un-parsed input. The tokenizer might not need to
59
- # parse the entire input
81
+ # The rest of the un-parsed input. The tokenizer might not need to parse the entire input
82
+ #
83
+ # @return [String]
60
84
  def rest
61
85
  @scanner.rest
62
86
  end
@@ -8,23 +8,26 @@ require_relative './language/syntax_error'
8
8
  module CSVPlusPlus
9
9
  # A container representing the operations that can be applied to a cell or row
10
10
  #
11
- # @attr expand [Expand]
12
- # @attr fontfamily [String]
13
- # @attr fontsize [String]
14
- # @attr halign ['left', 'center', 'right']
15
- # @attr valign ['top', 'center', 'bottom']
16
- # @attr note [String]
17
- # @attr numberformat [String]
18
- # @attr row_level [Boolean]
11
+ # @attr borders [Array<String>] The borders that will be set
12
+ # @attr expand [Expand] Whether this row expands into multiple rows
13
+ # @attr fontfamily [String] The font family
14
+ # @attr fontsize [Number] The font size
15
+ # @attr halign ['left', 'center', 'right'] Horizontal alignment
16
+ # @attr note [String] A note/comment on the cell
17
+ # @attr numberformat [String] A number format to apply to the value in the cell
18
+ # @attr row_level [Boolean] Is this a row modifier? If so it's values will apply to all cells in the row
19
+ # (unless overridden by the cell modifier)
19
20
  # @attr validation [Object]
21
+ # @attr valign ['top', 'center', 'bottom'] Vertical alignment
20
22
  #
21
- # @attr_writer borderstyle [String]
23
+ # @attr_writer borderstyle ['dashed', 'dotted', 'double', 'solid', 'solid_medium', 'solid_thick']
24
+ # The style of border on the cell
22
25
  #
23
26
  # @attr_reader bordercolor [String]
24
27
  # @attr_reader borders [Array<String>]
25
- # @attr_reader color [Color]
26
- # @attr_reader fontcolor [Color]
27
- # @attr_reader formats [Array<String>]
28
+ # @attr_reader color [Color] The background color of the cell
29
+ # @attr_reader fontcolor [Color] The font color of the cell
30
+ # @attr_reader formats [Array<String>] Bold/italics/underline/strikethrough formatting
28
31
  class Modifier
29
32
  attr_reader :bordercolor, :borders, :color, :fontcolor, :formats
30
33
  attr_writer :borderstyle
@@ -39,25 +42,32 @@ module CSVPlusPlus
39
42
  end
40
43
 
41
44
  # Set the color
45
+ #
42
46
  # @param hex_value [String]
47
+ #
48
+ # @return [Color]
43
49
  def color=(hex_value)
44
50
  @color = ::CSVPlusPlus::Color.new(hex_value)
45
51
  end
46
52
 
47
53
  # Assign a border
54
+ #
48
55
  # @param side ['top', 'left', 'bottom', 'right', 'all']
49
56
  def border=(side)
50
57
  @borders << side
51
58
  end
52
59
 
53
60
  # Does this have a border along +side+?
61
+ #
54
62
  # @param side ['top', 'left', 'bottom', 'right', 'all']
63
+ #
55
64
  # @return [Boolean]
56
65
  def border_along?(side)
57
66
  @borders.include?('all') || @borders.include?(side)
58
67
  end
59
68
 
60
69
  # Does this have a border along all sides?
70
+ #
61
71
  # @return [Boolean]
62
72
  def border_all?
63
73
  @borders.include?('all') \
@@ -65,65 +75,79 @@ module CSVPlusPlus
65
75
  end
66
76
 
67
77
  # Set the bordercolor
78
+ #
68
79
  # @param hex_value [String] formatted as '#000000', '#000' or '000000'
69
80
  def bordercolor=(hex_value)
70
81
  @bordercolor = ::CSVPlusPlus::Color.new(hex_value)
71
82
  end
72
83
 
73
84
  # Are there any borders set?
85
+ #
74
86
  # @return [Boolean]
75
87
  def any_border?
76
88
  !@borders.empty?
77
89
  end
78
90
 
79
91
  # Set the fontcolor
92
+ #
80
93
  # @param hex_value [String] formatted as '#000000', '#000' or '000000'
81
94
  def fontcolor=(hex_value)
82
95
  @fontcolor = ::CSVPlusPlus::Color.new(hex_value)
83
96
  end
84
97
 
85
98
  # Set a text format (bolid, italic, underline or strikethrough)
99
+ #
86
100
  # @param value ['bold', 'italic', 'underline', 'strikethrough']
87
101
  def format=(value)
88
102
  @formats << value
89
103
  end
90
104
 
91
105
  # Is the given format set?
106
+ #
92
107
  # @param type ['bold', 'italic', 'underline', 'strikethrough']
108
+ #
93
109
  # @return [Boolean]
94
110
  def formatted?(type)
95
111
  @formats.include?(type)
96
112
  end
97
113
 
98
114
  # Freeze the row from edits
115
+ #
116
+ # @return [true]
99
117
  def freeze!
100
118
  @frozen = true
101
119
  end
102
120
 
103
- # Is the row forzen?
121
+ # Is the row frozen?
122
+ #
104
123
  # @return [Boolean]
105
124
  def frozen?
106
125
  @frozen
107
126
  end
108
127
 
109
128
  # Mark this modifer as row-level
129
+ #
130
+ # @return [true]
110
131
  def row_level!
111
132
  @row_level = true
112
133
  end
113
134
 
114
135
  # Is this a row-level modifier?
136
+ #
115
137
  # @return [Boolean]
116
138
  def row_level?
117
139
  @row_level
118
140
  end
119
141
 
120
142
  # Is this a cell-level modifier?
143
+ #
121
144
  # @return [Boolean]
122
145
  def cell_level?
123
146
  !@row_level
124
147
  end
125
148
 
126
149
  # Style of border
150
+ #
127
151
  # @return [String]
128
152
  def borderstyle
129
153
  @borderstyle || 'solid'
@@ -137,6 +161,7 @@ module CSVPlusPlus
137
161
  end
138
162
 
139
163
  # Create a new modifier instance, with all values defaulted from +other+
164
+ #
140
165
  # @param other [Modifier]
141
166
  def take_defaults_from!(other)
142
167
  other.instance_variables.each do |property|