command_line_boss 0.1.0

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