csv_plus_plus 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './cli_flag'
3
4
  require_relative './google_options'
4
5
 
5
6
  module CSVPlusPlus
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CSVPlusPlus
4
- VERSION = '0.0.3'
4
+ VERSION = '0.0.4'
5
5
  public_constant :VERSION
6
6
  end
@@ -1,12 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './rubyxl_builder'
4
+
3
5
  module CSVPlusPlus
4
6
  module Writer
5
7
  # A class that can output a +Template+ to an Excel file
6
8
  class Excel < ::CSVPlusPlus::Writer::BaseWriter
7
- # write a +template+ to an Excel file
9
+ # write the +template+ to an Excel file
8
10
  def write(template)
9
- # TODO
11
+ ::CSVPlusPlus::Writer::RubyXLBuilder.new(
12
+ output_filename: @options.output_filename,
13
+ rows: template.rows,
14
+ sheet_name: @options.sheet_name
15
+ ).write
16
+ end
17
+
18
+ protected
19
+
20
+ def load_requires
21
+ require('rubyXL')
22
+ require('rubyXL/convenience_methods')
10
23
  end
11
24
  end
12
25
  end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './google_sheet_modifier'
4
+
3
5
  module CSVPlusPlus
4
6
  module Writer
5
- ##
6
7
  # Given +rows+ from a +Template+, build requests compatible with Google Sheets Ruby API
7
8
  # rubocop:disable Metrics/ClassLength
8
9
  class GoogleSheetBuilder
@@ -26,9 +27,7 @@ module CSVPlusPlus
26
27
  ::Google::Apis::SheetsV4
27
28
  end
28
29
 
29
- def sheets_color(color)
30
- sheets_ns::Color.new(red: color.red, green: color.green, blue: color.blue)
31
- end
30
+ def sheets_color(color); end
32
31
 
33
32
  def set_extended_value_type!(extended_value, value)
34
33
  v = value || ''
@@ -43,38 +42,20 @@ module CSVPlusPlus
43
42
  end
44
43
  end
45
44
 
46
- def build_text_format(mod)
47
- sheets_ns::TextFormat.new(
48
- bold: mod.formatted?('bold') || nil,
49
- italic: mod.formatted?('italic') || nil,
50
- strikethrough: mod.formatted?('strikethrough') || nil,
51
- underline: mod.formatted?('underline') || nil,
52
-
53
- font_family: mod.fontfamily,
54
- font_size: mod.fontsize,
55
-
56
- foreground_color: mod.fontcolor ? sheets_color(mod.fontcolor) : nil
57
- )
58
- end
59
-
60
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
45
+ # rubocop:disable Metrics/AbcSize
61
46
  def build_cell_format(mod)
62
47
  sheets_ns::CellFormat.new.tap do |cf|
63
- cf.text_format = build_text_format(mod)
48
+ cf.text_format = mod.text_format
64
49
 
65
- # TODO: are these not overwriting each other?
66
- cf.horizontal_alignment = 'LEFT' if mod.aligned?('left')
67
- cf.horizontal_alignment = 'RIGHT' if mod.aligned?('right')
68
- cf.horizontal_alignment = 'CENTER' if mod.aligned?('center')
69
- cf.vertical_alignment = 'TOP' if mod.aligned?('top')
70
- cf.vertical_alignment = 'BOTTOM' if mod.aligned?('bottom')
50
+ cf.horizontal_alignment = mod.halign if mod.halign
51
+ cf.vertical_alignment = mod.valign if mod.valign
71
52
 
72
- cf.background_color = sheets_color(mod.color) if mod.color
53
+ cf.background_color = mod.color if mod.color
73
54
 
74
- cf.number_format = sheets_ns::NumberFormat.new(type: mod.numberformat) if mod.numberformat
55
+ cf.number_format = mod.numberformat if mod.numberformat
75
56
  end
76
57
  end
77
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
58
+ # rubocop:enable Metrics/AbcSize
78
59
 
79
60
  def grid_range_for_cell(cell)
80
61
  sheets_ns::GridRange.new(
@@ -106,10 +87,10 @@ module CSVPlusPlus
106
87
  end
107
88
 
108
89
  def build_cell_data(cell)
109
- mod = cell.modifier
90
+ mod = ::CSVPlusPlus::Writer::GoogleSheetModifier.new(cell.modifier)
110
91
 
111
92
  sheets_ns::CellData.new.tap do |cd|
112
- cd.user_entered_format = build_cell_format(cell.modifier)
93
+ cd.user_entered_format = build_cell_format(mod)
113
94
  cd.note = mod.note if mod.note
114
95
 
115
96
  # XXX apply data validation
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CSVPlusPlus
4
+ module Writer
5
+ # Decorate a Modifier so it can be written to the Google Sheets API
6
+ class GoogleSheetModifier < ::SimpleDelegator
7
+ # Format the halign for Google Sheets
8
+ def halign
9
+ super&.upcase
10
+ end
11
+
12
+ # Format the valign for Google Sheets
13
+ def valign
14
+ super&.upcase
15
+ end
16
+
17
+ # Format the color for Google Sheets
18
+ def color
19
+ google_sheets_color(super) if super
20
+ end
21
+
22
+ # Format the fontcolor for Google Sheets
23
+ def fontcolor
24
+ google_sheets_color(super) if super
25
+ end
26
+
27
+ # Format the numberformat for Google Sheets
28
+ def numberformat
29
+ ::Google::Apis::SheetsV4::NumberFormat.new(type: super) if super
30
+ end
31
+
32
+ # Builds a SheetsV4::TextFormat with the underlying Modifier
33
+ def text_format
34
+ ::Google::Apis::SheetsV4::TextFormat.new(
35
+ bold: formatted?('bold') || nil,
36
+ italic: formatted?('italic') || nil,
37
+ strikethrough: formatted?('strikethrough') || nil,
38
+ underline: formatted?('underline') || nil,
39
+ font_family: fontfamily,
40
+ font_size: fontsize,
41
+ foreground_color: fontcolor
42
+ )
43
+ end
44
+
45
+ private
46
+
47
+ def google_sheets_color(color)
48
+ ::Google::Apis::SheetsV4::Color.new(
49
+ red: color.red_percent,
50
+ green: color.green_percent,
51
+ blue: color.blue_percent
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end
@@ -26,8 +26,8 @@ module CSVPlusPlus
26
26
  def write(template)
27
27
  auth!
28
28
 
29
- save_spreadsheet!
30
- save_spreadsheet_values!
29
+ fetch_spreadsheet!
30
+ fetch_spreadsheet_values!
31
31
 
32
32
  create_sheet! if @options.create_if_not_exists
33
33
 
@@ -58,7 +58,7 @@ module CSVPlusPlus
58
58
  @gs.authorization = ::Google::Auth.get_application_default(::AUTH_SCOPES)
59
59
  end
60
60
 
61
- def save_spreadsheet_values!
61
+ def fetch_spreadsheet_values!
62
62
  formatted_values = get_all_spreadsheet_values('FORMATTED_VALUE')
63
63
  formula_values = get_all_spreadsheet_values('FORMULA')
64
64
 
@@ -94,7 +94,7 @@ module CSVPlusPlus
94
94
  @spreadsheet.sheets.find { |s| s.properties.title.strip == @sheet_name.strip }
95
95
  end
96
96
 
97
- def save_spreadsheet!
97
+ def fetch_spreadsheet!
98
98
  @spreadsheet = @gs.get_spreadsheet(@sheet_id)
99
99
 
100
100
  return unless @sheet_name.nil?
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './rubyxl_modifier'
4
+
5
+ module CSVPlusPlus
6
+ module Writer
7
+ # Build a RubyXL workbook formatted according to the given +rows+
8
+ class RubyXLBuilder
9
+ attr_reader :output_filename, :rows
10
+
11
+ # initialize
12
+ def initialize(output_filename:, rows:, sheet_name:)
13
+ @rows = rows
14
+ @output_filename = output_filename
15
+ @workbook = open_workbook(sheet_name)
16
+ @worksheet = @workbook[sheet_name]
17
+ end
18
+
19
+ # write the given @rows in +sheet_name+ to +@output_filename+
20
+ def write
21
+ build_workbook!
22
+ @workbook.write(@output_filename)
23
+ end
24
+
25
+ private
26
+
27
+ def build_workbook!
28
+ @rows.each_with_index do |row, x|
29
+ row.cells.each_with_index do |cell, y|
30
+ modifier = ::CSVPlusPlus::Writer::RubyXLModifier.new(cell.modifier)
31
+
32
+ @worksheet.add_cell(x, y, cell.to_csv)
33
+ format_cell!(x, y, modifier)
34
+ end
35
+ end
36
+ end
37
+
38
+ def do_alignments!(cell, modifier)
39
+ cell.change_horizontal_alignment(modifier.halign) if modifier.halign
40
+ cell.change_vertical_alignment(modifier.valign) if modifier.valign
41
+ end
42
+
43
+ # rubocop:disable Metrics/MethodLength
44
+ def do_borders!(cell, modifier)
45
+ return unless modifier.any_border?
46
+
47
+ color = modifier.bordercolor
48
+ weight = modifier.border_weight
49
+
50
+ if modifier.border_all?
51
+ %i[top bottom left right].each do |direction|
52
+ # TODO: I can't support a weight and a color?
53
+ cell.change_border(direction, color || weight)
54
+ end
55
+ else
56
+ modifier.borders.each do |direction|
57
+ cell.change_border(direction, color || weight)
58
+ end
59
+ end
60
+ end
61
+ # rubocop:enable Metrics/MethodLength
62
+
63
+ def do_fill!(cell, modifier)
64
+ cell.change_fill(modifier.color.to_hex) if modifier.color
65
+ end
66
+
67
+ def do_formats!(cell, modifier)
68
+ cell.change_font_bold(true) if modifier.formatted?('bold')
69
+ cell.change_font_italics(true) if modifier.formatted?('italic')
70
+ cell.change_font_underline(true) if modifier.formatted?('underline')
71
+ cell.change_font_strikethrough(true) if modifier.formatted?('strikethrough')
72
+ end
73
+
74
+ def do_fonts!(cell, modifier)
75
+ cell.change_font_color(modifier.fontcolor.to_hex) if modifier.fontcolor
76
+ cell.change_font_name(modifier.fontfamily) if modifier.fontfamily
77
+ cell.change_font_size(modifier.fontsize) if modifier.fontsize
78
+ end
79
+
80
+ def do_number_formats!(cell, modifier)
81
+ return unless modifier.numberformat
82
+
83
+ cell.set_number_format(modifier.number_format_code)
84
+ # TODO: this is annoying... we have to set the contents with the correct type of object
85
+ cell.change_contents(cell.value)
86
+ end
87
+
88
+ def format_cell!(row_index, cell_index, modifier)
89
+ @worksheet.sheet_data[row_index][cell_index].tap do |cell|
90
+ do_alignments!(cell, modifier)
91
+ do_borders!(cell, modifier)
92
+ do_fill!(cell, modifier)
93
+ do_fonts!(cell, modifier)
94
+ do_formats!(cell, modifier)
95
+ do_number_formats!(cell, modifier)
96
+ end
97
+ end
98
+
99
+ def open_workbook(sheet_name)
100
+ if ::File.exist?(@output_filename)
101
+ ::RubyXL::Parser.parse(@output_filename).tap do |workbook|
102
+ workbook.add_worksheet(sheet_name) unless workbook[sheet_name]
103
+ end
104
+ else
105
+ ::RubyXL::Workbook.new.tap do |workbook|
106
+ workbook.worksheets[0].sheet_name = sheet_name
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CSVPlusPlus
4
+ # Writer
5
+ module Writer
6
+ # Build a RubyXL-decorated Modifier class adds some support for Excel
7
+ class RubyXLModifier < ::SimpleDelegator
8
+ # https://www.rubydoc.info/gems/rubyXL/RubyXL/NumberFormats
9
+ # https://support.microsoft.com/en-us/office/number-format-codes-5026bbd6-04bc-48cd-bf33-80f18b4eae68
10
+ NUM_FMT_IDS = {
11
+ currency: 5,
12
+ date: 14,
13
+ date_time: 22,
14
+ number: 1,
15
+ percent: 9,
16
+ text: 49,
17
+ time: 21,
18
+ scientific: 48
19
+ }.freeze
20
+ private_constant :NUM_FMT_IDS
21
+
22
+ # https://www.rubydoc.info/gems/rubyXL/2.3.0/RubyXL
23
+ # ST_BorderStyle = %w{ none thin medium dashed dotted thick double hair mediumDashed dashDot mediumDashDot
24
+ # dashDotDot slantDashDot }
25
+ BORDER_STYLES = {
26
+ dashed: 'dashed',
27
+ dotted: 'dotted',
28
+ double: 'double',
29
+ solid: 'thin',
30
+ solid_medium: 'medium',
31
+ solid_thick: 'thick'
32
+ }.freeze
33
+ private_constant :BORDER_STYLES
34
+
35
+ # The excel-specific number format code
36
+ def number_format_code
37
+ ::RubyXL::NumberFormats::DEFAULT_NUMBER_FORMATS.find_by_format_id(
38
+ # rubocop:disable Lint/ConstantResolution
39
+ NUM_FMT_IDS[numberformat.to_sym]
40
+ # rubocop:enable Lint/ConstantResolution
41
+ ).format_code
42
+ end
43
+
44
+ # The excel-specific border weight
45
+ def border_weight
46
+ # rubocop:disable Lint/ConstantResolution
47
+ BORDER_STYLES[borderstyle.to_sym]
48
+ # rubocop:enable Lint/ConstantResolution
49
+ end
50
+ end
51
+ end
52
+ end
@@ -7,7 +7,6 @@ require_relative './writer/google_sheets'
7
7
  require_relative './writer/open_document'
8
8
 
9
9
  module CSVPlusPlus
10
- ##
11
10
  # Various strategies for writing to various formats (excel, google sheets, CSV, OpenDocument)
12
11
  module Writer
13
12
  # Return an instance of a writer depending on the given +options+
@@ -17,8 +16,8 @@ module CSVPlusPlus
17
16
  case options.output_filename
18
17
  when /\.csv$/ then ::CSVPlusPlus::Writer::CSV.new(options)
19
18
  when /\.ods$/ then ::CSVPlusPlus::Writer::OpenDocument.new(options)
20
- when /\.xls$/ then ::CSVPlusPlus::Writer::Excel.new(options)
21
- else raise(::StandardError, "Unsupported extension: #{options.output_filename}")
19
+ when /\.xl(sx|sm|tx|tm)$/ then ::CSVPlusPlus::Writer::Excel.new(options)
20
+ else raise(::CSVPlusPlus::Error, "Unsupported file extension: #{options.output_filename}")
22
21
  end
23
22
  end
24
23
  end
data/lib/csv_plus_plus.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'csv_plus_plus/error'
3
4
  require_relative 'csv_plus_plus/language/compiler'
4
5
  require_relative 'csv_plus_plus/options'
5
6
  require_relative 'csv_plus_plus/writer'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: csv_plus_plus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick Carroll
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-05 00:00:00.000000000 Z
11
+ date: 2023-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-apis-sheets_v4
@@ -39,61 +39,61 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.3'
41
41
  - !ruby/object:Gem::Dependency
42
- name: bundler
42
+ name: rubyXL
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '2'
48
- type: :development
47
+ version: '3.4'
48
+ type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '2'
54
+ version: '3.4'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rake
56
+ name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '13'
61
+ version: '2'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '13'
68
+ version: '2'
69
69
  - !ruby/object:Gem::Dependency
70
- name: rubocop
70
+ name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '1.4'
75
+ version: '13'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '1.4'
82
+ version: '13'
83
83
  - !ruby/object:Gem::Dependency
84
- name: solargraph
84
+ name: rubocop
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '1.4'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: '1.4'
97
97
  description: "A programming language built on top of CSV. You can define functions
98
98
  and variables to use in your spreadsheet, \nthen compile it to Excel, CSV, Google
99
99
  Sheets, etc.\n"
@@ -108,8 +108,10 @@ files:
108
108
  - bin/csv++
109
109
  - lib/csv_plus_plus.rb
110
110
  - lib/csv_plus_plus/cell.rb
111
+ - lib/csv_plus_plus/cli_flag.rb
111
112
  - lib/csv_plus_plus/code_section.rb
112
113
  - lib/csv_plus_plus/color.rb
114
+ - lib/csv_plus_plus/error.rb
113
115
  - lib/csv_plus_plus/expand.rb
114
116
  - lib/csv_plus_plus/google_options.rb
115
117
  - lib/csv_plus_plus/graph.rb
@@ -144,13 +146,22 @@ files:
144
146
  - lib/csv_plus_plus/writer/csv.rb
145
147
  - lib/csv_plus_plus/writer/excel.rb
146
148
  - lib/csv_plus_plus/writer/google_sheet_builder.rb
149
+ - lib/csv_plus_plus/writer/google_sheet_modifier.rb
147
150
  - lib/csv_plus_plus/writer/google_sheets.rb
148
151
  - lib/csv_plus_plus/writer/open_document.rb
152
+ - lib/csv_plus_plus/writer/rubyxl_builder.rb
153
+ - lib/csv_plus_plus/writer/rubyxl_modifier.rb
149
154
  homepage: https://github.com/patrickomatic/csv-plus-plus
150
155
  licenses:
151
156
  - MIT
152
157
  metadata:
153
158
  rubygems_mfa_required: 'true'
159
+ bug_tracker_uri: https://github.com/patrickomatic/csv-plus-plus/issues
160
+ documentation_uri: https://www.rubydoc.info/gems/csv_plus_plus/
161
+ github_repo: git://github.com/patrickomatic/csv_plus_plus
162
+ homepage_uri: https://github.com/patrickomatic/csv_plus_plus
163
+ source_code_uri: https://github.com/patrickomatic/csv_plus_plus
164
+ changelog_uri: https://github.com/patrickomatic/csv_plus_plus/blob/main/CHANGELOG.md
154
165
  post_install_message:
155
166
  rdoc_options: []
156
167
  require_paths: