csv_plus_plus 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66b51b9e9784f89236625da57b647867b502b50acad209d1e98619fd94fb6f70
4
- data.tar.gz: f4815f9a10637ee82a4f4f1e32afbd9db47bf728eb3992bb77ce75d9fb460092
3
+ metadata.gz: ea5b029c524b401348cc42399514c9fb689b13bfcf7298664429f69c85ae6607
4
+ data.tar.gz: 5bb79395742dcd89bf3b4ca7ede92a9a41a2c3ddefc4dff1b4232c285117373d
5
5
  SHA512:
6
- metadata.gz: 5a983659e3033642f9ff185adca67897a3510578ef765f1cc4f65e8df6ec1f1ecf8b767e6860c3af00c773b6b5d4229bc4699e071a38b04a3f31ec55a225cf76
7
- data.tar.gz: '0684df3fc8678b6061d572da196bcdfb24f224009c9fa61b64b769178aa18dd6697c7a9f4dba3ab93e15b3a79ecfcd350f23abf1c117bee9026a0b9ba47e6e6c'
6
+ metadata.gz: 4a1bf4ccf98486b64ed69f34e701e9c4c69aea2b971c569aab7f6345ca721ccbde01570449033be23eeea37ec59d244dbb2692f27bfbfaae6069c84e23ea988a
7
+ data.tar.gz: 8cef1aa204255588787b3e3138ec282a9b45c174b83e647a91d0476aca199ee4ee8640f11c40418cded6026016a4383e1e1df7600cdfa5c5524d25a6c953fb76
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ ## v0.0.4
2
+
3
+ - Excel support
4
+
5
+ ## v0.0.3
6
+
7
+ - Fix the gem package to include the bin/ file
8
+
9
+ ## v0.0.2
10
+
11
+ - First publish to rubygems.org
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-community-brightgreen.svg)](https://rubystyle.guide)
2
+
3
+ # csv++
4
+
5
+ A tool that allows you to programatically author spreadsheets in your favorite text editor and write their results to CSV, Google Sheets, Excel and other spreadsheet formats. This allows you to write a spreadsheet template, check it into git and push changes out to spreadsheets using typical dev tools.
6
+
7
+ ## Template Language
8
+
9
+ A `csvpp` file consists of a (optional) code section and a CSV section separated by `---`. In the code section you can define variables and functions that can be used in the CSV below it. For example:
10
+
11
+ ```
12
+ fees := 0.50 # my broker charges $0.50 a trade
13
+
14
+ price := cellref(C)
15
+ quantity := cellref(D)
16
+
17
+ def profit() (price * quantity) - fees
18
+
19
+ ---
20
+ ![[format=bold/align=center]]Date,Ticker,Price,Quantity,Total,Fees
21
+ ![[expand]],[[format=bold]],,,"=PROFIT()",$$fees
22
+ ```
23
+
24
+ ## Predefined Variables
25
+
26
+ * `$$rownum` - The current row number. The first row of the spreadsheet starts at 1
27
+
28
+ ## Predefined Functions
29
+
30
+ * `cellref(CELL)` - Returns a reference to the `CELL` relative to the current row. If the current `$$rownum` is `2`, then `CELLREF("C")` returns a reference to cell `C2`.
31
+
32
+ ## Modifiers
33
+
34
+ Modifiers can change the formatting of a cell or row, apply validation, change alignment, etc. All of the normal rules of CSV apply, with the addition that each cell can have modifiers (specified in `[[`/`]]` for cells and `![[`/`]]` for rows):
35
+
36
+ ```
37
+ foo,[[...]]bar,baz
38
+ ```
39
+
40
+ specifying formatting or various other modifiers to the cell. Additionally a row can start with:
41
+
42
+ ```
43
+ ![[...]]foo,bar,baz
44
+ ```
45
+
46
+ which will apply that modifier to all cells in the row.
47
+
48
+ ### Examples
49
+
50
+ * Align the second cell left, align the last cell to the center and make it bold and italicized:
51
+
52
+ ```
53
+ Date,[[align=left]]Amount,Quantity,[[align=center/format=bold italic]]Price
54
+ ```
55
+
56
+ * Underline and center-align an entire row:
57
+
58
+ ```
59
+ ![[align=center/format=underline]]Date,Amount,Quantity,Price
60
+ ```
61
+
62
+ * A header for the first row, then some formulas that repeat for each row for the rest of the spreadsheet:
63
+
64
+ ```
65
+ ![[align=center/format=bold]]Date,Price,Quantity,Profit
66
+ ![[expand=1:]],,,"=MULTIPLY(cellref(B), cellref(C))"
67
+ ```
68
+
69
+ ## Setup (Google Sheets)
70
+
71
+ Just install it via rubygems (homebrew and debian packages are in the works):
72
+
73
+ `$ gem install csv_plus_plus`
74
+
75
+ ### Publishing to Google Sheets
76
+
77
+ * Go to the [GCP developers console](https://console.cloud.google.com/projectselector2/apis/credentials?pli=1&supportedpurview=project), create a service account and export keys for it to `~/.config/gcloud/application_default_credentials.json`
78
+ * "Share" the spreadsheet with the email associated with the service account
79
+
80
+ ## CLI Arguments
81
+
82
+ ```
83
+ Usage: csv++ [options]
84
+ -b, --backup Create a backup of the spreadsheet before applying changes.
85
+ -g, --google-sheet-id SHEET_ID The id of the sheet - you can extract this from the URL: https://docs.google.com/spreadsheets/d/< ... SHEET_ID ... >/edit#gid=0
86
+ -c, --create Create the sheet if it doesn't exist. It will use --sheet-name if specified
87
+ -k, --key-values KEY_VALUES A comma-separated list of key=values which will be made available to the template
88
+ -n, --sheet-name SHEET_NAME The name of the sheet to apply the template to
89
+ -v, --verbose Enable verbose output
90
+ -x, --offset-columns OFFSET Apply the template offset by OFFSET cells
91
+ -y, --offset-rows OFFSET Apply the template offset by OFFSET rows
92
+ -h, --help Show help information
93
+ ```
94
+
95
+ ## Usage Examples
96
+
97
+ ```
98
+ # apply my_taxes_template.csvpp to an existing Google Sheet with name "Taxes 2022"
99
+ $ csv++ --sheet-name "Taxes 2022" --sheet-id "[...]" my_taxes_template.csvpp
100
+
101
+ # take input from stdin, supply a variable ($$rate = 1) and apply to the "Stocks" spreadsheet
102
+ $ cat stocks.csvpp | csv++ -k "rate=1" -n "Stocks" -i "[...]"
103
+ ```
data/bin/csv++ ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'optparse'
5
+ require_relative '../lib/csv_plus_plus'
6
+
7
+ options = ::CSVPlusPlus::Options.new
8
+
9
+ option_parser =
10
+ ::OptionParser.new do |parser|
11
+ parser.on('-h', '--help', 'Show help information') do
12
+ puts(parser)
13
+ exit
14
+ end
15
+
16
+ ::SUPPORTED_CSVPP_FLAGS.each do |flag|
17
+ parser.on(flag.short_flag, flag.long_flag, flag.description) do |v|
18
+ flag.handler.call(options, v)
19
+ end
20
+ end
21
+ end
22
+
23
+ option_parser.parse!
24
+
25
+ error_message = options.validate
26
+ unless error_message.nil?
27
+ warn(error_message)
28
+ puts(option_parser)
29
+ exit(1)
30
+ end
31
+
32
+ begin
33
+ ::CSVPlusPlus.apply_template_to_sheet!(::ARGF.read, ::ARGF.filename, options)
34
+ rescue ::CSVPlusPlus::Error => e
35
+ if e.is_a?(::CSVPlusPlus::Language::SyntaxError)
36
+ warn(options.verbose ? e.to_verbose_trace : e.to_trace)
37
+ else
38
+ warn(e.message)
39
+ end
40
+
41
+ exit(1)
42
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './google_options'
4
+
5
+ module CSVPlusPlus
6
+ # Individual CLI flags that a user can supply
7
+ class CliFlag
8
+ attr_reader :short_flag, :long_flag, :description, :handler
9
+
10
+ # initialize
11
+ def initialize(short_flag, long_flag, description, handler)
12
+ @short_flag = short_flag
13
+ @long_flag = long_flag
14
+ @description = description
15
+ @handler = handler
16
+ end
17
+
18
+ # to_s
19
+ def to_s
20
+ "#{@short_flag}, #{@long_flag} #{@description}"
21
+ end
22
+ end
23
+ end
24
+
25
+ SUPPORTED_CSVPP_FLAGS = [
26
+ ::CSVPlusPlus::CliFlag.new(
27
+ '-b',
28
+ '--backup',
29
+ 'Create a backup of the spreadsheet before applying changes.',
30
+ ->(options, _v) { options.backup = true }
31
+ ),
32
+ ::CSVPlusPlus::CliFlag.new(
33
+ '-c',
34
+ '--create',
35
+ "Create the sheet if it doesn't exist. It will use --sheet-name if specified",
36
+ ->(options, _v) { options.create_if_not_exists = true }
37
+ ),
38
+ ::CSVPlusPlus::CliFlag.new(
39
+ '-g SHEET_ID',
40
+ '--google-sheet-id SHEET_ID',
41
+ 'The id of the sheet - you can extract this from the URL: ' \
42
+ 'https://docs.google.com/spreadsheets/d/< ... SHEET_ID ... >/edit#gid=0',
43
+ ->(options, v) { options.google_sheet_id = v }
44
+ ),
45
+ ::CSVPlusPlus::CliFlag.new(
46
+ '-k',
47
+ '--key-values KEY_VALUES',
48
+ 'A comma-separated list of key=values which will be made available to the template',
49
+ lambda do |options, v|
50
+ options.key_values =
51
+ begin
52
+ [v.split('=')].to_h
53
+ rescue ::StandardError
54
+ {}
55
+ end
56
+ end
57
+ ),
58
+ ::CSVPlusPlus::CliFlag.new(
59
+ '-n SHEET_NAME',
60
+ '--sheet-name SHEET_NAME',
61
+ 'The name of the sheet to apply the template to',
62
+ ->(options, v) { options.sheet_name = v }
63
+ ),
64
+ ::CSVPlusPlus::CliFlag.new(
65
+ '-o OUTPUT_FILE',
66
+ '--output OUTPUT_FILE',
67
+ 'The file to write to (must be .csv, .ods, .xls)',
68
+ ->(options, v) { options.output_filename = v }
69
+ ),
70
+ ::CSVPlusPlus::CliFlag.new('-v', '--verbose', 'Enable verbose output', ->(options, _v) { options.verbose = true }),
71
+ ::CSVPlusPlus::CliFlag.new(
72
+ '-x OFFSET',
73
+ '--offset-columns OFFSET',
74
+ 'Apply the template offset by OFFSET cells',
75
+ ->(options, v) { options.offset[0] = v }
76
+ ),
77
+ ::CSVPlusPlus::CliFlag.new(
78
+ '-y OFFSET',
79
+ '--offset-rows OFFSET',
80
+ 'Apply the template offset by OFFSET rows',
81
+ ->(options, v) { options.offset[1] = v }
82
+ )
83
+ ].freeze
@@ -3,20 +3,54 @@
3
3
  module CSVPlusPlus
4
4
  # A color value
5
5
  class Color
6
- attr_reader :red, :green, :blue
6
+ attr_reader :red_hex, :green_hex, :blue_hex
7
7
 
8
8
  # create an instance from a string like "#FFF" or "#FFFFFF"
9
9
  def initialize(hex_string)
10
- @red, @green, @blue =
11
- hex_string
12
- .gsub(/^#?/, '')
13
- .match(/(\w\w?)(\w\w?)(\w\w?)/)
14
- .captures
15
- .map do |s|
16
- 255 / (s.length == 2 ? s : s + s).to_i(16)
17
- rescue ::StandardError
18
- 0
19
- end
10
+ @red_hex, @green_hex, @blue_hex = hex_string
11
+ .gsub(/^#?/, '')
12
+ .match(/([0-9a-f]{1,2})([0-9a-f]{1,2})([0-9a-f]{1,2})/i)
13
+ &.captures
14
+ &.map { |s| s.length == 1 ? s + s : s }
15
+ end
16
+
17
+ # The percent (decimal between 0-1) of red
18
+ def red_percent
19
+ hex_to_percent(@red_hex)
20
+ end
21
+
22
+ # The percent (decimal between 0-1) of green
23
+ def green_percent
24
+ hex_to_percent(@green_hex)
25
+ end
26
+
27
+ # The percent (decimal between 0-1) of blue
28
+ def blue_percent
29
+ hex_to_percent(@blue_hex)
30
+ end
31
+
32
+ # to_hex
33
+ def to_hex
34
+ [@red_hex, @green_hex, @blue_hex].join
35
+ end
36
+
37
+ # to_s
38
+ def to_s
39
+ "Color(r: #{@red_hex}, g: #{@green_hex}, b: #{@blue_hex})"
40
+ end
41
+
42
+ # ==
43
+ def ==(other)
44
+ other.is_a?(self.class) &&
45
+ other.red_hex == @red_hex &&
46
+ other.green_hex == @green_hex &&
47
+ other.blue_hex == @blue_hex
48
+ end
49
+
50
+ private
51
+
52
+ def hex_to_percent(hex)
53
+ hex.to_i(16) / 255
20
54
  end
21
55
  end
22
56
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CSVPlusPlus
4
+ # An error thrown by our code (generally to be handled at the top level bin/ command)
5
+ class Error < StandardError
6
+ end
7
+ end
@@ -54,11 +54,6 @@ module CSVPlusPlus
54
54
  include ::TSort
55
55
  alias tsort_each_node each_key
56
56
 
57
- # create a +DependencyGraph+ from a +Hash+
58
- def self.from_hash(hash)
59
- self[hash.map { |k, v| [k, v] }]
60
- end
61
-
62
57
  # sort each child
63
58
  def tsort_each_child(node, &)
64
59
  fetch(node).each(&)
@@ -14,7 +14,6 @@ require_relative 'scope'
14
14
 
15
15
  module CSVPlusPlus
16
16
  module Language
17
- ##
18
17
  # Encapsulates the parsing and building of objects (+Template+ -> +Row+ -> +Cell+).
19
18
  # Variable resolution is delegated to the +Scope+
20
19
  # rubocop:disable Metrics/ClassLength
@@ -127,8 +127,6 @@ module CSVPlusPlus
127
127
 
128
128
  # this will throw a syntax error if it doesn't exist (which is what we want)
129
129
  return ::BUILTIN_FUNCTIONS[id] if ::BUILTIN_FUNCTIONS.key?(id)
130
-
131
- @runtime.raise_syntax_error('Unknown function', fn_id)
132
130
  end
133
131
 
134
132
  def apply_arguments(function, function_call)
@@ -4,7 +4,7 @@ module CSVPlusPlus
4
4
  module Language
5
5
  ##
6
6
  # An error that can be thrown for various syntax errors
7
- class SyntaxError < StandardError
7
+ class SyntaxError < ::CSVPlusPlus::Error
8
8
  # initialize
9
9
  def initialize(message, bad_input, runtime, wrapped_error: nil)
10
10
  @bad_input = bad_input.to_s
@@ -6,32 +6,20 @@ require_relative './expand'
6
6
  require_relative './language/syntax_error'
7
7
 
8
8
  module CSVPlusPlus
9
- ##
10
9
  # A container representing the operations that can be applied to a cell or row
11
10
  class Modifier
12
11
  attr_reader :bordercolor, :borders, :color, :fontcolor, :formats
13
12
  attr_writer :borderstyle
14
- attr_accessor :expand, :fontfamily, :fontsize, :note, :numberformat, :row_level, :validation
13
+ attr_accessor :expand, :fontfamily, :fontsize, :halign, :valign, :note, :numberformat, :row_level, :validation
15
14
 
16
15
  # initialize
17
16
  def initialize(row_level: false)
18
17
  @row_level = row_level
19
18
  @freeze = false
20
- @align = ::Set.new
21
19
  @borders = ::Set.new
22
20
  @formats = ::Set.new
23
21
  end
24
22
 
25
- # Set an align format. +direction+ must be 'center', 'left', 'right', 'bottom'
26
- def align=(direction)
27
- @align << direction
28
- end
29
-
30
- # Is it aligned to a given direction?
31
- def aligned?(direction)
32
- @align.include?(direction)
33
- end
34
-
35
23
  # Set the color. hex_value is a String
36
24
  def color=(hex_value)
37
25
  @color = ::CSVPlusPlus::Color.new(hex_value)
@@ -110,12 +98,13 @@ module CSVPlusPlus
110
98
  # to_s
111
99
  def to_s
112
100
  # TODO... I dunno, not sure how to manage this
113
- "Modifier(row_level: #{@row_level} align: #{@align} format: #{@formats} font_size: #{@font_size})"
101
+ "Modifier(row_level: #{@row_level} halign: #{@halign} valign: #{@valign} format: #{@formats} " \
102
+ "font_size: #{@font_size})"
114
103
  end
115
104
 
116
105
  # Create a new modifier instance, with all values defaulted from +other+
117
106
  def take_defaults_from!(other)
118
- instance_variables.each do |property|
107
+ other.instance_variables.each do |property|
119
108
  value = other.instance_variable_get(property)
120
109
  instance_variable_set(property, value.clone)
121
110
  end