command_line_boss 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.
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'command_line_boss', path: File.expand_path(`git rev-parse --show-toplevel`.chomp)
6
+
7
+ gem 'create_spreadsheet', path: '.'
8
+
9
+ gem 'csv', '~> 3.3'
10
+ gem 'fuubar', '~> 2.5'
11
+ gem 'rake', '~> 13.0'
12
+ gem 'rspec', '~> 3.0'
13
+ gem 'rubocop', '~> 1.64'
14
+ gem 'simplecov', '~> 0.22'
15
+ gem 'simplecov-lcov', '~> 0.8'
16
+ gem 'turnip', '~> 4.4'
@@ -0,0 +1,314 @@
1
+ # Example: create-spreadsheet
2
+
3
+ Ideally, with a command line parser class, you want a class that you can simply pass
4
+ in ARGV to the parser and get the values parsed from the command line using getter
5
+ methods.
6
+
7
+ This example shows step-by-step how to write a class that implements parsing the
8
+ command line for creating a Google Docs Spreadsheet.
9
+
10
+ * [Step 1: Define requirements](#step-1-define-requirements)
11
+ * [Step 2: Design the command line](#step-2-design-the-command-line)
12
+ * [Step 3: Implement accessors and set default values for result attributes](#step-3-implement-accessors-and-set-default-values-for-result-attributes)
13
+ * [Step 4: Write tests](#step-4-write-tests)
14
+ * [Step 5: Define options](#step-5-define-options)
15
+ * [Step 6: Define validations](#step-6-define-validations)
16
+ * [Step 7: Parse remaining command line arguments](#step-7-parse-remaining-command-line-arguments)
17
+ * [Step 8: Make the --help output look good](#step-8-make-the---help-output-look-good)
18
+
19
+ ## Step 1: Define requirements
20
+
21
+ For this example, write a class that implements parsing the command line that meets
22
+ the following requirements:
23
+
24
+ * Optionally specify the **title** of the spreadsheet. If not specified, use the
25
+ default title assigned by Google for new spreadsheets (usually "Untitled
26
+ Spreadsheet")
27
+ * Optionally specify a list of **sheets** to create and give an optional CSV data
28
+ file to preload into each sheet. If no sheets are specified, one blank sheet is
29
+ created by Google with the default title (usually "Sheet 1"). If a data file is not
30
+ specified for a given sheet, that sheet should be left blank.
31
+ * Optionally specify a list of **permissions** to add to the spreadsheet. The user
32
+ must be able to specify the type of the permission (user, group, domain, or
33
+ anyone), the subject of the permission (depending on the type either an email
34
+ address, domain name or nothing), and the role being granted (organizer,
35
+ fileOrganizer, writer, commenter, or reader).
36
+ * Optionally specify the Google Drive **folder** to create the spreadsheet in. If not
37
+ given, the spreadsheet will be created in the root drive directory of the user
38
+ whose credentials are being used to make the request.
39
+
40
+ Once the command line is parsed, return this information in the following attributes:
41
+
42
+ * **title**: the optional title of the spreadsheet
43
+ * **sheets**: an array of sheets each having attributes **title** and **data** (read from the given CSV)
44
+ * **permissions**: an array of permissions each having attributes **type**, **subject**, and **role**
45
+ * **folder_id**: the id of the Google Drive folder in which to create the spreadsheet
46
+
47
+ ## Step 2: Design the command line
48
+
49
+ Design the command line following the
50
+ [Google developer documentation style guide for command line syntax](https://developers.google.com/style/code-syntax).
51
+
52
+ Document what the `--help` option SHOULD output in the following sections:
53
+
54
+ ```text
55
+ BANNER
56
+ HEADER
57
+ OPTIONS
58
+ FOOTER
59
+ ```
60
+
61
+ For this example, here is what the --help output should be:
62
+
63
+ ```text
64
+ Create a new Google Spreadsheet
65
+
66
+ Usage:
67
+
68
+ create_spreadsheet [SPREADSHEET_TITLE] \
69
+ [--sheet=TITLE [--data=DATA_FILE]]... \
70
+ [--folder=FOLDER_ID] \
71
+ [--permission=PERMISSION_SPEC]...
72
+
73
+ Options:
74
+ --sheet=TITLE Title of a sheet to create
75
+ --data=DATA_FILE Data file for the last sheet
76
+ --folder=FOLDER_ID Create the spreadsheet in the given Google Drive folder
77
+ --permission=PERMISSION_SPEC Set permissions on the spreadsheet
78
+
79
+ DATA_FILE := A file containing data in CSV format
80
+ PERMISSION_SPEC := {user:EMAIL:ROLE | group:EMAIL:ROLE | domain:DOMAIN:ROLE | anyone:ROLE}
81
+ ROLE := {organizer | fileOrganizer | writer | commenter | reader}
82
+ ```
83
+
84
+ ## Step 3: Implement accessors and set default values for result attributes
85
+
86
+ In the requirements, there were 4 public attributes defined for this command line
87
+ parser: title, sheets, permissions, and folder_id. Use the private `set_defaults`
88
+ method to set the default values for each attribute.
89
+
90
+ ```ruby
91
+ # Parse the command line for creating a Google Spreadsheet
92
+ #
93
+ # @example
94
+ # options = CreateSpreadsheetCLI.new.call(ARGV)
95
+ #
96
+ # @!attribute title [String, nil] the title to give the new spreadsheet or nil for the default
97
+ # @!attribute sheets [Array<Sheet>] the sheets to create including title and data for each
98
+ # @!attribute permissions [Array<Permission>] the permissions to add to the spreadsheet
99
+ # @!attribute folder_id [String] the id of the Google Drive folder in which to create the spreadsheet
100
+ #
101
+ # @api public
102
+ #
103
+ class CreateSpreadsheetCli < CommandLineBoss
104
+ attr_reader :title, :sheets, :folder, :permissions
105
+
106
+ private
107
+
108
+ def set_defaults
109
+ @title = nil
110
+ @sheets = []
111
+ @permissions = []
112
+ @folder_id = nil
113
+ end
114
+ end
115
+ ```
116
+
117
+ In this case, it would be advantageous to create a few supporting classes and
118
+ constants to return sheets and permissions. Add the following code to the
119
+ CreateSpreadsheetCli class.
120
+
121
+ ```ruby
122
+ # Sheets specified at the command line
123
+ #
124
+ # @!attribute title [String, nil] the title of the sheet to create or nil for the default title
125
+ # @!attribute data [Array<Array<Object>>, nil] the data to write to the sheet or nil
126
+ #
127
+ # @api public
128
+ Sheet = Struct.new(:title, :data)
129
+
130
+ # Permissions specified at the command line
131
+ #
132
+ # @!attribute type [String] must be one of VALID_PERM_TYPES
133
+ # @!attribute subject [String, nil] the name of the subject the permission is given to
134
+ #
135
+ # * If the type is 'user' or 'group', must be a valid email address
136
+ # * If type is 'domain', must be a valid domain name
137
+ # * If type is anyone, must be nil
138
+ #
139
+ # @!attribute role [String] myst be one of VALID_PERM_ROLES
140
+ Permission = Struct.new(:type, :subject, :role)
141
+
142
+ VALID_PERM_TYPES = %w[user group domain anyone].freeze
143
+ VALID_PERM_ROLES = %w[organizer fileOrganizer writer commenter reader].freeze
144
+ ```
145
+
146
+ ## Step 4: Write tests
147
+
148
+ Write tests in your favorite testing framework that assert various permutations of
149
+ command line arguments result either in:
150
+
151
+ * The expected attrbiute values, or
152
+ * The expected exception was raised
153
+
154
+ Tests for this example can be found in spec/create_spreadsheet.feature`
155
+ Tests for this interface might include:
156
+
157
+ * Nothing is given on the command line
158
+ * A spreadsheet title is given
159
+ * A sheet is defined with a title
160
+ * A sheet is defined with a title and data
161
+ * Multiple sheets are defined both with data
162
+ * Multiple sheets are defined only one with data
163
+ * A user permission is given
164
+ * A group permission is given
165
+ * A domain permission is given
166
+ * An anyone permission is given
167
+ * Multiple permissions are given
168
+ * A folder is given
169
+ * A sheet given without a name
170
+ * A permission is given without a permission spec
171
+ * A invalid permission is given
172
+ * The permission spec has an invalid type
173
+ * A permission spec has an invalid role
174
+ * A subject is given for an anyone permission
175
+ * A subject is not given for a user permission
176
+ * Data is given without a path
177
+ * Data is given with an non-existant path
178
+ * The folder option is given twice
179
+ * A same sheet name is given twice
180
+ * Data is given twice for the same sheet
181
+
182
+ ## Step 5: Define options
183
+
184
+ Define private methods whose name is define_*_option.
185
+
186
+ Methods MUST be private or they won't be called.
187
+
188
+ Add any errors to the `error_messages` array.
189
+
190
+ ```ruby
191
+ private
192
+
193
+ def define_sheet_title_option
194
+ parser.on('--sheet-title=TITLE', 'Title of a sheet to create') do |title|
195
+ sheets << Sheet.new(title:, data: nil)
196
+ end
197
+ end
198
+
199
+ def define_sheet_data_option
200
+ parser.on('--sheet-data=DATA_FILE', 'Data file for the last sheet') do |data_file|
201
+ sheets << Sheet.new(title: nil, data: nil) if sheets.empty?
202
+ if sheets.last.data
203
+ error_messages << 'Only one --sheet-data option is allowed per --sheet-title'
204
+ else
205
+ sheets.last.data = CSV.read(data_file)
206
+ end
207
+ end
208
+ end
209
+
210
+ def define_folder_option
211
+ parser.on('--folder=FOLDER_ID', 'Create the spreadsheet to the given folder') do |folder_id|
212
+ if @folder_id
213
+ error_messages << 'Only one --folder option is allowed'
214
+ else
215
+ @folder_id = folder_id
216
+ end
217
+ end
218
+ end
219
+
220
+ PERMISSION_SPEC_REGEXP = /
221
+ ^
222
+ (?<type>[^:]+)
223
+ (?:
224
+ :(?<subject>[^:]+)
225
+ )?
226
+ :(?<role>[^:]+)
227
+ $
228
+ /x
229
+
230
+ def define_permission_option
231
+ parser.on('--permission=PERMISSION_SPEC', 'Set permissions on the spreadsheet') do |permission_spec|
232
+ match = permission_spec.match(PERMISSION_SPEC_REGEXP)
233
+ unless match
234
+ error_messages << "Invalid permission spec: #{permission_spec}"
235
+ next
236
+ end
237
+ permissions << Permission.new(
238
+ permission_spec:, type: match[:type], subject: match[:subject], role: match[:role]
239
+ )
240
+ end
241
+ end
242
+ ```
243
+
244
+ ## Step 6: Define validations
245
+
246
+ Define private methods whose name is validate_*.
247
+
248
+ Methods MUST be private or they won't be called.
249
+
250
+ Add any errors to the `error_messages` array.
251
+
252
+ ```ruby
253
+ def validate_permission_types
254
+ permissions.each do |permission|
255
+ unless VALID_PERMISSION_TYPES.include?(permission.type)
256
+ error_messages << "Invalid permission type: #{permission.type}"
257
+ end
258
+ end
259
+ end
260
+
261
+ def validate_permission_roles
262
+ permissions.each do |permission|
263
+ unless VALID_PERMISSION_ROLES.include?(permission.role)
264
+ error_messages << "Invalid permission role: #{permission.role}"
265
+ end
266
+ end
267
+ end
268
+
269
+ def validate_permission_subjects
270
+ permissions.each do |permission|
271
+ if permission.type == 'anyone' && permission.subject
272
+ error_messages << "Permission subject for type 'anyone' should be blank in #{permission.permission_spec}"
273
+ end
274
+ if permission.type != 'anyone' && !permission.subject
275
+ error_messages << "Permission subject missing in #{permission.permission_spec}"
276
+ end
277
+ end
278
+ end
279
+ ```
280
+
281
+ ## Step 7: Parse remaining command line arguments
282
+
283
+ ```ruby
284
+ def parse_arguments
285
+ @spreadsheet_title = args.shift
286
+ end
287
+ ```
288
+
289
+ ## Step 8: Make the --help output look good
290
+
291
+ ```ruby
292
+ def banner = <<~TEXT
293
+ Create a new Google Spreadsheet
294
+ TEXT
295
+
296
+ # Usage text for the command line help
297
+ # @api private
298
+ def usage = <<~TEXT
299
+ Usage:
300
+
301
+ create_spreadsheet SPREADSHEET_TITLE \
302
+ [--sheet-title=TITLE [--sheet-data=DATA_FILE]]... \
303
+ [--folder=FOLDER_ID }] \
304
+ [--permission=PERMISSION_SPEC]...
305
+ TEXT
306
+
307
+ # Footer text for the command line help
308
+ # @api private
309
+ def footer = <<~TEXT
310
+ PERMISSION_SPEC := {user:EMAIL:ROLE | group:EMAIL:ROLE | domain:DOMAIN:ROLE | anyone:ROLE}
311
+ ROLE := {organizer | fileOrganizer | writer | commenter | reader}
312
+ DATA_FILE := A file containing data in CSV format
313
+ TEXT
314
+ ```
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ task default: %i[spec rubocop build]
4
+
5
+ require 'rake/clean'
6
+
7
+ # RSpec
8
+
9
+ require 'rake'
10
+ require 'rspec/core/rake_task'
11
+
12
+ RSpec::Core::RakeTask.new do |t|
13
+ t.pattern = './spec/**/*{_spec.rb,.feature}'
14
+ end
15
+
16
+ CLEAN << 'coverage'
17
+ CLEAN << 'test'
18
+ CLEAN << '.rspec_status'
19
+ CLEAN << 'rspec-report.xml'
20
+
21
+ # Bundler Gem Build
22
+
23
+ require 'bundler'
24
+ require 'bundler/gem_tasks'
25
+
26
+ begin
27
+ Bundler.setup(:default, :development)
28
+ rescue Bundler::BundlerError => e
29
+ warn e.message
30
+ warn 'Run `bundle install` to install missing gems'
31
+ exit e.status_code
32
+ end
33
+
34
+ CLEAN << 'pkg'
35
+ CLOBBER << 'Gemfile.lock'
36
+
37
+ # Rubocop
38
+
39
+ require 'rubocop/rake_task'
40
+
41
+ RuboCop::RakeTask.new do |t|
42
+ t.options = %w[
43
+ --format fuubar
44
+ --format json --out rubocop-report.json
45
+ --display-cop-names
46
+ --config .rubocop.yml
47
+ ]
48
+ end
49
+
50
+ CLEAN << 'rubocop-report.json'
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'create_spreadsheet'
5
+ spec.version = '1.0.0'
6
+ spec.authors = ['James Couball']
7
+ spec.email = ['jcouball@yahoo.com']
8
+
9
+ spec.summary = 'A command line utility to create a Google Spreadsheet'
10
+ spec.description = ''
11
+ spec.license = 'MIT'
12
+ spec.required_ruby_version = '>= 3.1.0'
13
+
14
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
15
+
16
+ spec.homepage = 'https://github.com/main-branch/create_spreadsheet'
17
+
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = 'https://github.com/main-branch/create_spreadsheet'
20
+ spec.metadata['changelog_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}/file/CHANGELOG.md"
21
+ spec.metadata['documentation_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ gemspec = File.basename(__FILE__)
26
+ spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|
27
+ ls.readlines("\x0", chomp: true).reject do |f|
28
+ (f == gemspec) ||
29
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
30
+ end
31
+ end
32
+ spec.bindir = 'exe'
33
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
34
+ spec.require_paths = ['lib']
35
+
36
+ # For more information and examples about making a new gem, check out our
37
+ # guide at: https://bundler.io/guides/creating_gem.html
38
+ spec.metadata['rubygems_mfa_required'] = 'true'
39
+ end
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'create_spreadsheet'
5
+ require 'csv'
6
+ require 'pp'
7
+
8
+ options = CreateSpreadsheet::CommandLine.new.parse(ARGV)
9
+
10
+ if options.failed?
11
+ warn "Parsing failed:\n#{options.error_messages.join("\n")}"
12
+ exit 1
13
+ end
14
+
15
+ # options =
16
+ # begin
17
+ # CreateSpreadsheet::CommandLine.new.parse(ARGV)
18
+ # rescue CommandLineBoss::Error => e
19
+ # warn "ERROR: #{e.parser.error_messages.join("\nERROR: ")}"
20
+ # exit 1
21
+ # end
22
+
23
+ puts <<~OPTIONS
24
+ Creating spreadsheet with title: #{options.title.pretty_inspect}
25
+ Creating sheets: #{options.sheets.pretty_inspect}
26
+ In folder: #{options.folder_id.pretty_inspect}
27
+ Adding permissions: #{options.permissions.pretty_inspect}
28
+ OPTIONS
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'command_line_boss'
4
+ require 'csv'
5
+
6
+ module CreateSpreadsheet
7
+ # Sheet`s specified at the command line
8
+ #
9
+ # @!attribute title [String, nil] the title of the sheet to create or nil for the default title
10
+ # @!attribute data [Array<Array<Object>>, nil] the data to write to the sheet or nil
11
+ #
12
+ # @api public
13
+ Sheet = Struct.new(:title, :data, keyword_init: true)
14
+
15
+ # Permissions specified at the command line
16
+ #
17
+ # @!attribute permission_spec [String] the unparsed permission spec as given at the command line
18
+ # @!attribute type [String] must be one of VALID_PERM_TYPES
19
+ # @!attribute subject [String, nil] the name of the subject the permission is given to
20
+ #
21
+ # * If the type is 'user' or 'group', must be a valid email address
22
+ # * If type is 'domain', must be a valid domain name
23
+ # * If type is anyone, must be nil
24
+ #
25
+ # @!attribute role [String] myst be one of VALID_PERM_ROLES
26
+ Permission = Struct.new(:permission_spec, :type, :subject, :role, keyword_init: true)
27
+
28
+ # The list of valid permission types the user can give
29
+ # @return [Array<String>]
30
+ # @api private
31
+ VALID_PERM_TYPES = %w[user group domain anyone].freeze
32
+
33
+ # The list of valid permission roles the user can give
34
+ # @return [Array<String>]
35
+ # @api private
36
+ VALID_PERM_ROLES = %w[organizer fileOrganizer writer commenter reader].freeze
37
+
38
+ # The regular expression for parsing a permission spec
39
+ # @return [Regexp]
40
+ PERMISSION_SPEC_REGEXP = /
41
+ ^
42
+ (?<type>[^:]+)
43
+ (?:
44
+ :(?<subject>[^:]+)
45
+ )?
46
+ :(?<role>[^:]+)
47
+ $
48
+ /x
49
+
50
+ # A command line interface for creating spreadsheets
51
+ #
52
+ # @!attribute [r] title
53
+ # @return [String, nil] The title of the spreadsheet to create or nil for the default title
54
+ #
55
+ # @!attribute [r] sheets
56
+ # @return [Array<Sheet>] The sheets to create in the spreadsheet
57
+ #
58
+ # @!attribute [r] permissions
59
+ # @return [Array<Permission>] The list of permissions to add to the spreadsheet
60
+ #
61
+ # @!attribute [r] folder_id
62
+ # @return [String, nil] The ID of the folder to move the spreadsheet to
63
+ #
64
+ # @example Create a spreadsheet named "My Spreadsheet" with a default sheet named "Sheet1"
65
+ # ARGV #=> ["My Spreadsheet"]
66
+ # options = CommandLineParser.new.call(ARGV)
67
+ # options.spreadsheet_title #=> "My Spreadsheet"
68
+ #
69
+ # @api pubic
70
+ #
71
+ class CommandLine < CommandLineBoss
72
+ attr_reader :title, :sheets, :permissions, :folder_id
73
+
74
+ private
75
+
76
+ # Set the attribute default values
77
+ # @return [void]
78
+ # @api private
79
+ def set_defaults
80
+ @title = nil
81
+ @sheets = []
82
+ @permissions = []
83
+ @folder_id = nil
84
+ end
85
+
86
+ # Remove the title from the remaining arguments
87
+ # @return [void]
88
+ # @api private
89
+ def parse_arguments
90
+ @title = @args.shift
91
+ end
92
+
93
+ # Define the --sheet option
94
+ # @return [void]
95
+ # @api private
96
+ def define_sheet_option
97
+ parser.on('--sheet=TITLE', 'Title of a sheet to create') do |title|
98
+ if sheets.any? { |sheet| sheet.title.downcase == title.downcase }
99
+ add_error_message("The sheet #{title} was given more than once")
100
+ end
101
+
102
+ sheets << Sheet.new(title:, data: nil)
103
+ end
104
+ end
105
+
106
+ # Read the csv data file
107
+ # @param data_file [String] the name of the data file
108
+ # @return [Array<Array<Object>>, nil] the data in the file or nil if the file is not found
109
+ # @api private
110
+ def read_data_file(data_file)
111
+ CSV.parse(File.read(data_file))
112
+ rescue Errno::ENOENT
113
+ add_error_message "Data file not found: #{data_file}"
114
+ nil
115
+ end
116
+
117
+ # Define the --data option
118
+ # @return [void]
119
+ # @api private
120
+ def define_data_option
121
+ parser.on('--data=DATA_FILE', 'Data file for the last named sheet') do |data_file|
122
+ sheets << Sheet.new(title: nil, data: nil) if sheets.empty?
123
+ if sheets.last.data
124
+ add_error_message 'Only one data file is allowed per sheet'
125
+ else
126
+ sheets.last.data = read_data_file(data_file)
127
+ end
128
+ end
129
+ end
130
+
131
+ # Define the --permission option
132
+ # @return [void]
133
+ # @api private
134
+ def define_permission_option
135
+ parser.on('--permission=PERMISSION_SPEC', 'Set permissions on the spreadsheet') do |permission_spec|
136
+ match = permission_spec.match(PERMISSION_SPEC_REGEXP)
137
+ unless match
138
+ add_error_message "Invalid permission: #{permission_spec}"
139
+ next
140
+ end
141
+ permissions << Permission.new(
142
+ permission_spec:, type: match[:type], subject: match[:subject], role: match[:role]
143
+ )
144
+ end
145
+ end
146
+
147
+ # Define the --folder option
148
+ # @return [void]
149
+ # @api private
150
+ def define_folder_option
151
+ parser.on('--folder=FOLDER_ID', 'Create the spreadsheet to the given folder') do |folder_id|
152
+ if @folder_id
153
+ add_error_message 'Only one --folder option is allowed'
154
+ else
155
+ @folder_id = folder_id
156
+ end
157
+ end
158
+ end
159
+
160
+ # Validate the permission role value
161
+ # @return [void]
162
+ # @api private
163
+ def validate_permission_role
164
+ permissions.each do |p|
165
+ add_error_message "Invalid permission role: #{p.role}" unless VALID_PERM_ROLES.include?(p.role)
166
+ end
167
+ end
168
+
169
+ # Validate that no permissions of type 'anyone' has a subject
170
+ # @return [void]
171
+ # @api private
172
+ def validate_permission_anyone_subject
173
+ permissions.each do |p|
174
+ add_error_message 'An anyone permission must not have a subject' if p.type == 'anyone' && p.subject
175
+ end
176
+ end
177
+
178
+ # Validate that all permissions that are not type 'anyone' have a subject
179
+ # @return [void]
180
+ # @api private
181
+ def validate_permission_other_subject
182
+ permissions.each do |p|
183
+ add_error_message "A #{p.type} permission must have a subject" if p.type != 'anyone' && !p.subject
184
+ end
185
+ end
186
+
187
+ # Validate that all permissions have a valid type
188
+ # @return [void]
189
+ # @api private
190
+ def validate_permission_type
191
+ permissions.each do |p|
192
+ add_error_message "Invalid permission type: #{p.type}" unless VALID_PERM_TYPES.include?(p.type)
193
+ end
194
+ end
195
+
196
+ include CommandLineBoss::HelpOption
197
+
198
+ # Banner shown at the top of the help message
199
+ # @return [String]
200
+ # @api private
201
+ def banner = <<~BANNER
202
+ Create a new Google Spreadsheet'
203
+
204
+ Usage:
205
+
206
+ create_spreadsheet [SPREADSHEET_TITLE] \\
207
+ [--sheet=TITLE [--data=DATA_FILE]]... \\
208
+ [--folder=FOLDER_ID] \\
209
+ [--permission=PERMISSION_SPEC]...
210
+ BANNER
211
+
212
+ # Footer shown at the bottom of the help message
213
+ # @return [String]
214
+ # @api private
215
+ def footer = <<~FOOTER
216
+ DATA_FILE := A file containing data in CSV format
217
+ PERMISSION_SPEC := {user:EMAIL:ROLE | group:EMAIL:ROLE | domain:DOMAIN:ROLE | anyone:ROLE}
218
+ ROLE := {organizer | fileOrganizer | writer | commenter | reader}
219
+ FOOTER
220
+ end
221
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'command_line_boss'
4
+
5
+ # CreateSpreadsheet is a command line parser for creating spreadsheets
6
+ module CreateSpreadsheet; end
7
+
8
+ require 'create_spreadsheet/command_line'