resme 0.4.0 → 0.5.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8abd7d17f0d2b9a777cba680826c21d6ee882f3f3df0c74ba086e1d617d4b6b
4
- data.tar.gz: b34b33c2f2d0f3beae3bef713efba2e39b05537d8a208380e6392271e0cbcb09
3
+ metadata.gz: de3845cee948da0b1588b074a720662d5ea22a8c1cb1176aad2fa09f6f7cab3f
4
+ data.tar.gz: 83ae22f7d7731406fdd3940e97c9e39c9a61b07336e95f2efc369cae719687f9
5
5
  SHA512:
6
- metadata.gz: fe74aa311a4dd110c26a0760cf6d2f8cb1ba1ee8e587f2a64d00ff6aa420a2a7f6c37b1eec392ddf1b28abbe49ffb94d6b26d5f43f2797ba9fd7cc9874ebf5c1
7
- data.tar.gz: 3f2ae60c5c8a4e7f8135aa7f43aafa081f913dc8dd6c1b4c6065606447a41b6afe414ae4f32e0541fc06284227d94628f31f59e80af02aee2147c054032137c8
6
+ metadata.gz: 32d94c5d909f54888d2a8fa184aaa05f8a9d8075d6f3f19ff6654bf23e58f82e54e96d84ea08de148548ed0260396c6d9790b6dbc0e3992c295157b5713bb952
7
+ data.tar.gz: 41386481b15b5b14ba86efbd23b29331341b22f91ebcc70497d8293609414a15e8c06518ad441e0cd26f78b552a47e7d125c42bc495f17def2bbd16314fe52b5
data/CHANGELOG.org ADDED
@@ -0,0 +1,60 @@
1
+ #+TITLE: CHANGELOG
2
+ #+AUTHOR: Adolfo Villafiorita
3
+
4
+ * Version 0.5.1
5
+
6
+ A bug-fixing release.
7
+
8
+ More in details:
9
+
10
+ - [bug] Add dependency to =classy_hash=
11
+ - [doc] Fix links to source code and Changelog.
12
+ - [doc] Merge Release History and Changelog.
13
+ - [doc] Fix version numbers in headers of Changelog (1.5.0 -> 0.5.0)
14
+
15
+ * Version 0.5.0
16
+ - [user] New command =view= allows to view the template used
17
+ for generating a resume in a specific format
18
+ - [user] Check command is now based on ClassyHash
19
+ - [code] Various changes to the code
20
+
21
+ * Version 0.4.0
22
+
23
+ Version 0.4.0 refactors all generation commands under =generate=, provides
24
+ new filtering options, adds =-e= option (for custom templates), and
25
+ refactors various portions of code. It also revises this document
26
+ and fixes some minor bugs.
27
+
28
+ More in details:
29
+
30
+ - [user] New option =--skip= allows to skip some top-level sections.
31
+ You mileage might vary, as some formats might require the
32
+ information you are trying to skip
33
+ - [user] New command =generate= is now used to generate the Resume
34
+ in different formats.
35
+ - [user] New option =--erb= allows to specify a custom template for
36
+ generating the resume. Use it in conjunction with =view= (released
37
+ in version 1.5.0) to jump-start your layout.
38
+
39
+ * Version 0.3.2 and 0.3.1
40
+
41
+ Version 0.3.2 and 0.3.1 fix errors with the Europass format: lists of
42
+ projects, interests, ... are now properly formatted.
43
+
44
+ * Version 0.3
45
+
46
+ Version 0.3 Introduces output to org-mode, introduces references for the CV,
47
+ improves output to JSON, adds a =check= command, removes useless blank
48
+ lines in the output, supports =-%>= in the ERB templates, fixes
49
+ various typos in the documentation, introduces various new formatting
50
+ functions, to simplify template generation
51
+
52
+ * Version 0.2
53
+
54
+ Version 0.2 improves output of volunteering activities and other information
55
+ in the Europass and *significantly improves error and warning
56
+ reporting*
57
+
58
+ * Version 0.1
59
+
60
+ Version 0.1 is the first release
data/Gemfile.lock ADDED
@@ -0,0 +1,20 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ resme (0.5.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ rake (12.3.3)
10
+
11
+ PLATFORMS
12
+ x86_64-linux
13
+
14
+ DEPENDENCIES
15
+ bundler (~> 2.3.20)
16
+ rake (~> 12.0)
17
+ resme!
18
+
19
+ BUNDLED WITH
20
+ 2.3.20
data/README.org CHANGED
@@ -17,20 +17,20 @@ needs).
17
17
 
18
18
  Add this line to your application's Gemfile:
19
19
 
20
- #+BEGIN_SRC ruby
21
- gem 'resme'
22
- #+END_SRC
20
+ #+begin_example ruby
21
+ gem 'resme'
22
+ #+end_example
23
23
 
24
24
  And then execute:
25
25
 
26
- #+BEGIN_EXAMPLE
27
- $ bundle
28
- #+END_EXAMPLE
26
+ #+begin_example sh
27
+ bundle
28
+ #+end_example
29
29
 
30
30
  Or install it yourself as:
31
31
 
32
- #+BEGIN_EXAMPLE
33
- $ gem install resme
32
+ #+begin_example sh
33
+ gem install resme
34
34
  #+END_EXAMPLE
35
35
 
36
36
  ** Usage
@@ -40,9 +40,9 @@ Or install it yourself as:
40
40
 
41
41
  Start with:
42
42
 
43
- #+BEGIN_EXAMPLE
43
+ #+begin_example sh
44
44
  resme init
45
- #+END_EXAMPLE
45
+ #+end_example
46
46
 
47
47
  which generates a YML template for your resume in the current directory.
48
48
  Comments in the YML file should help you fill the various entries.
@@ -51,7 +51,7 @@ are not relevant for your resume.
51
51
 
52
52
  You can now generate a resume using one of the existing formats:
53
53
 
54
- #+begin_example
54
+ #+begin_example sh
55
55
  resme --to org resume.yml
56
56
  #+end_example
57
57
 
@@ -64,18 +64,33 @@ Supported formats include:
64
64
  - =json=: JSON format (https://jsonresume.org/)
65
65
 
66
66
  If you are not satisfied with the provided templates, you can write
67
- your own (see below). In this case, however, =resme= is mainly an ERB
68
- renderer.
67
+ your own (see below).
69
68
 
70
- Remarks:
69
+ Notice that you can specify more than one YML file in input. This allows you to
70
+ store data about your resume in different files, if you like to do so
71
+ (e.g., work experiences could be in one file and talks in another).
72
+ The YML files are merged before processing them.
71
73
 
72
- - You can specify more than one YML file in input. This allows you to
73
- store data about your resume in different files, if you like to do so
74
- (e.g., work experiences could be in one file and talks in another).
75
- The YML files are merged before processing them.
76
- - The output filename is optional. If you do not specify one, the resume
77
- is generated to =resume-YYYY.MM.DD.format=, where =YYYY-MM-DD= is
78
- today's date and =format= is the chosen output format
74
+ Full syntax:
75
+
76
+ #+begin_src shell :results raw output :wrap example
77
+ resme help
78
+ #+end_src
79
+
80
+ #+RESULTS:
81
+ #+begin_example
82
+ resme command [options] [args]
83
+ Available commands:
84
+ check resume.yml # Check syntax of resume.yml
85
+ list resume.yml # List main sections in resume.yml
86
+ version # print version information
87
+ view --template FORMAT # Print template used for format
88
+ generate [options] resume.yml ... # output resume
89
+ console # enter the console
90
+ man # print resme manual page
91
+ help [command] # print command usage
92
+ init [options] # generate an empty resume.yml file
93
+ #+end_example
79
94
 
80
95
  ** Checking validity
81
96
  :PROPERTIES:
@@ -85,9 +100,9 @@ Remarks:
85
100
  Use the =check= command to verify whether your YAML file conforms with
86
101
  the intended syntax.
87
102
 
88
- #+BEGIN_SRC ruby
103
+ #+begin_example sh
89
104
  resme check resume.yaml
90
- #+END_SRC
105
+ #+end_example
91
106
 
92
107
  ** Dates in the resume
93
108
  :PROPERTIES:
@@ -110,28 +125,36 @@ The third and the forth format allows you to enter "partial" dates
110
125
  :END:
111
126
 
112
127
  The resumes are generated from the YML matter using ERB templates. The
113
- provided output formats should support different back ends (Org Mode
114
- and Markdown easily allow for generation of PDFs, HTML, and ODT, to
115
- mention a few).
116
-
117
- However, if you want, you can define your own templates. All the data
118
- in the resume is made available in the =data= variable. Thus, for
119
- instance, the following code snippets generates a list of all the work
120
- experiences:
121
-
122
- #+BEGIN_EXAMPLE
123
- <% data.work each do |exp| %>
124
- - <%= exp.who %>
125
- From: <%= exp.from %> till: <%= exp.till %>
128
+ provided output formats should support different back ends. Org Mode
129
+ and Markdown, for instance, easily allow for generation of PDFs, HTML,
130
+ and ODT, to mention a few.
131
+
132
+ However, if you want, you can define your own templates.
133
+
134
+ Use the command =view= to print one of the templates used by =resme=
135
+ and build from that.
136
+
137
+ #+begin_example sh
138
+ resme view --template md
139
+ #+end_example
140
+
141
+ Notice that all the data in the resume is made available in the =data=
142
+ variable. Thus, for instance, the following code snippets generates a
143
+ list of all the work experiences:
144
+
145
+ #+begin_example xml
146
+ <% data["work"] each do |exp| %>
147
+ - <%= exp["who"] %>
148
+ From: <%= exp["from"] %> till: <%= exp["till"] %>
126
149
  <% end %>
127
- #+END_EXAMPLE
150
+ #+end_example
128
151
 
129
- To specify your own ERB template use the option =-e= (=--erb=). Thus,
130
- for instance:
152
+ To specify your own ERB template to build your resume use the option
153
+ =-e= (=--erb=). Thus, for instance:
131
154
 
132
- #+BEGIN_EXAMPLE
133
- $ resme render -e template.md.erb [-o output_filename] file.yaml ...
134
- #+END_EXAMPLE
155
+ #+begin_example sh
156
+ resme render -e template.md.erb [-o output_filename] file.yaml ...
157
+ #+end_example
135
158
 
136
159
  uses =template.md.erb= to generate the resume.
137
160
 
@@ -200,12 +223,16 @@ https://github.com/avillafiorita/resme.
200
223
  The gem is available as open source under the terms of the
201
224
  [[http://opensource.org/licenses/MIT][MIT License]].
202
225
 
226
+ ** Change Log
227
+
228
+ In [[file:./CHANGELOG.org][CHANGELOG.org]]
229
+
203
230
  ** Roadmap
204
231
  :PROPERTIES:
205
232
  :CUSTOM_ID: roadmap
206
233
  :END:
207
234
 
208
- In =doc/todo.org= ... guess what is my preferred editor!
235
+ In [[file:./doc/todo.org][todo.org]] ... guess what is my preferred editor!
209
236
 
210
237
  ** Bugs
211
238
  :PROPERTIES:
@@ -228,18 +255,4 @@ Unknown number of unknown bugs.
228
255
  :CUSTOM_ID: release-history
229
256
  :END:
230
257
 
231
- - *0.4.0* refactors all generation commands under =generate=, provides
232
- new filtering options, adds =-e= option (for custom templates), and
233
- refactors various portions of code. It also revises this document
234
- and fixes some minor bugs.
235
- - *0.3.2* and *0.3.1* fix errors with the Europass format: lists of
236
- projects, interests, ... are now properly formatted.
237
- - *0.3* introduces output to org-mode, introduces references for the CV,
238
- improves output to JSON, adds a =check= command, removes useless blank
239
- lines in the output, supports =-%>= in the ERB templates, fixes
240
- various typos in the documentation, introduces various new formatting
241
- functions, to simplify template generation
242
- - *0.2* improves output of volunteering activities and other information
243
- in the Europass and *significantly improves error and warning
244
- reporting*
245
- - *0.1* is the first release
258
+ Moved to [[file:./CHANGELOG.org][CHANGELOG.org]].
@@ -5,7 +5,6 @@ require "date"
5
5
  require "yaml"
6
6
  require "erb"
7
7
  require "json"
8
- require "kwalify"
9
8
 
10
9
  module Resme
11
10
  module CommandSemantics
@@ -93,24 +92,24 @@ module Resme
93
92
  # APP SPECIFIC COMMANDS
94
93
  #
95
94
  def self.check(opts, argv)
96
- path = File.join(File.dirname(__FILE__), "/../templates/schema.yml")
97
- schema = Kwalify::Yaml.load_file(path)
98
-
99
- # create validator
100
- validator = Kwalify::Validator.new(schema)
101
- # load document
102
- document = Kwalify::Yaml.load_file(argv[0])
103
- # validate
104
- errors = validator.validate(document)
105
-
106
- # show errors
107
- if errors && !errors.empty?
108
- for e in errors
109
- puts "[#{e.path}] #{e.message}"
95
+ begin
96
+ document = YAML.load_file(argv[0], permitted_classes: [Date])
97
+ rescue Psych::SyntaxError => ex
98
+ puts "The file #{argv[0]} has an invalid structure."
99
+ puts ex.message
100
+ exit 1
101
+ end
102
+
103
+ begin
104
+ errors = ResumeStructureValidator.validate(document)
105
+ rescue Exception => ex
106
+ puts "The files #{argv[0]} does not validate"
107
+ ex.entries.each do |error|
108
+ puts "#{error[:full_path]}: #{error[:message]}"
110
109
  end
111
- else
112
- puts "The file #{argv[0]} validates."
110
+ exit 1
113
111
  end
112
+ puts "The file #{argv[0]} has a valid structure."
114
113
  end
115
114
 
116
115
  def self.init(opts, argv)
@@ -141,6 +140,12 @@ module Resme
141
140
  end
142
141
  end
143
142
 
143
+ def self.view(opts, argv)
144
+ format = opts[:template] == "europass" ? "xml" : opts[:template]
145
+ template = File.join(File.dirname(__FILE__), "/../templates/resume.#{format}.erb")
146
+ puts File.read template
147
+ end
148
+
144
149
  def self.generate(opts, argv)
145
150
  format = opts[:to] == "europass" ? "xml" : opts[:to]
146
151
  output = opts[:output] || "resume-#{Date.today}.#{format}"
@@ -217,6 +217,35 @@ module Resme
217
217
  }
218
218
  end
219
219
 
220
+ def self.view_opts
221
+ opts = OptionParser.new do |o|
222
+ o.banner = "view --template FORMAT # Print template used for format"
223
+ o.on("-t", "--template FORMAT", String, "View template for FORMAT")
224
+ end
225
+
226
+ help = <<-EOS
227
+ NAME
228
+ #{opts.banner}
229
+
230
+ SYNOPSYS
231
+ #{opts.to_s}
232
+
233
+ DESCRIPTION
234
+ Print template used for FORMAT
235
+
236
+ EXAMPLES
237
+ resme view --template md
238
+ EOS
239
+
240
+ {
241
+ view: {
242
+ name: :view,
243
+ options: opts,
244
+ help: help.gsub(" ", "")
245
+ }
246
+ }
247
+ end
248
+
220
249
  def self.generate_opts
221
250
  opts = OptionParser.new do |o|
222
251
  o.banner = "generate [options] resume.yml ... # output resume"
@@ -0,0 +1,293 @@
1
+ require "classy_hash"
2
+ require "date"
3
+
4
+ module Resme
5
+ module ResumeStructureValidator
6
+ OPTIONAL_STRING = [:optional, String, NilClass]
7
+ OPTIONAL_PARTIAL_DATE = [:optional, Date, String, Integer, NilClass]
8
+ PARTIAL_DATE = [Date, String, Integer]
9
+
10
+ def self.validate(loaded_yaml)
11
+ errors = []
12
+ ClassyHash.validate(
13
+ loaded_yaml,
14
+ SCHEMA,
15
+ errors: errors,
16
+ strict: true,
17
+ raise_errors: true,
18
+ full: true
19
+ )
20
+ errors
21
+ end
22
+
23
+ #
24
+ # This defines the structure of resume.yml
25
+ # We validate it with ClassyHash
26
+ #
27
+ SCHEMA = {
28
+ "basics" => {
29
+ "first_name" => String,
30
+ "middle_name" => OPTIONAL_STRING,
31
+ "last_name" => String,
32
+ "title" => OPTIONAL_STRING,
33
+ "picture" => OPTIONAL_STRING,
34
+ "birthdate" => [:optional, Date, NilClass],
35
+ "nationality" => OPTIONAL_STRING,
36
+ "marital_status" => OPTIONAL_STRING,
37
+ "gender" => OPTIONAL_STRING
38
+ },
39
+ "contacts" => [[ {
40
+ "label" => String,
41
+ "value" => String
42
+ } ]],
43
+ "addresses" => [[ {
44
+ "label" => String,
45
+ "street" => OPTIONAL_STRING,
46
+ "zip_code" => [:optional, String, Integer, NilClass],
47
+ "city" => OPTIONAL_STRING,
48
+ "country" => OPTIONAL_STRING
49
+ } ]],
50
+ "web_presence" => [:optional,
51
+ [[
52
+ {
53
+ "label" => String,
54
+ "value" => String
55
+ },
56
+ ]],
57
+ NilClass
58
+ ],
59
+ "summary" => OPTIONAL_STRING,
60
+ "work" => [:optional,
61
+ [[
62
+ {
63
+ "who" => OPTIONAL_STRING,
64
+ "website" => OPTIONAL_STRING,
65
+ "address" => OPTIONAL_STRING,
66
+ "till" => OPTIONAL_PARTIAL_DATE,
67
+ "from" => OPTIONAL_PARTIAL_DATE,
68
+ "role" => String,
69
+ "summary" => String,
70
+ "details" => OPTIONAL_STRING
71
+ },
72
+ ]],
73
+ NilClass
74
+ ],
75
+ "teaching" => [:optional,
76
+ [[
77
+ {
78
+ "who" => String,
79
+ "school" => OPTIONAL_STRING,
80
+ "address" => OPTIONAL_STRING,
81
+ "till" => OPTIONAL_PARTIAL_DATE,
82
+ "from" => OPTIONAL_PARTIAL_DATE,
83
+ "role" => String,
84
+ "subject" => String,
85
+ "summary" => OPTIONAL_STRING,
86
+ "details" => OPTIONAL_STRING
87
+ }
88
+ ]],
89
+ NilClass
90
+ ],
91
+ "projects" => [:optional,
92
+ [[
93
+ {
94
+ "name" => String,
95
+ "size" => OPTIONAL_STRING,
96
+ "who" => OPTIONAL_STRING,
97
+ "till" => OPTIONAL_PARTIAL_DATE,
98
+ "from" => OPTIONAL_PARTIAL_DATE,
99
+ "role" => String,
100
+ "summary" => OPTIONAL_STRING,
101
+ }
102
+ ]],
103
+ NilClass
104
+ ],
105
+ "other" => [:optional,
106
+ [[
107
+ {
108
+ "who" => OPTIONAL_STRING,
109
+ "till" => OPTIONAL_PARTIAL_DATE,
110
+ "from" => OPTIONAL_PARTIAL_DATE,
111
+ "role" => String,
112
+ "summary" => OPTIONAL_STRING,
113
+ }
114
+ ]],
115
+ NilClass
116
+ ],
117
+ "committees" => [:optional,
118
+ [[
119
+ {
120
+ "who" => String,
121
+ "role" => String,
122
+ "editions" => [String, Integer],
123
+ "url" => OPTIONAL_STRING,
124
+ }
125
+ ]],
126
+ NilClass
127
+ ],
128
+ "volunteer" => [:optional,
129
+ [[
130
+ {
131
+ "who" => String,
132
+ "where" => OPTIONAL_STRING,
133
+ "date" => OPTIONAL_PARTIAL_DATE,
134
+ "till" => OPTIONAL_PARTIAL_DATE,
135
+ "from" => OPTIONAL_PARTIAL_DATE,
136
+ "role" => String,
137
+ "summary" => OPTIONAL_STRING,
138
+ }
139
+ ]],
140
+ NilClass
141
+ ],
142
+ "visits" => [:optional,
143
+ [[
144
+ {
145
+ "who" => String,
146
+ "address" => OPTIONAL_STRING,
147
+ "till" => OPTIONAL_PARTIAL_DATE,
148
+ "from" => OPTIONAL_PARTIAL_DATE,
149
+ "role" => String,
150
+ "summary" => OPTIONAL_STRING,
151
+ }
152
+ ]],
153
+ NilClass
154
+ ],
155
+ "education" => [:optional,
156
+ [[
157
+ {
158
+ "degree" => OPTIONAL_STRING,
159
+ "topic" => OPTIONAL_STRING,
160
+ "school" => String,
161
+ "address" => OPTIONAL_STRING,
162
+ "date" => OPTIONAL_PARTIAL_DATE,
163
+ "till" => OPTIONAL_PARTIAL_DATE,
164
+ "from" => OPTIONAL_PARTIAL_DATE,
165
+ "publish" => TrueClass,
166
+ "score" => [:optional, String, Integer, NilClass],
167
+ }
168
+ ]],
169
+ NilClass
170
+ ],
171
+ "publications" => [:optional,
172
+ [[
173
+ {
174
+ "title" => String,
175
+ "authors" => String,
176
+ "publisher" => String,
177
+ "date" => PARTIAL_DATE,
178
+ "url" => OPTIONAL_STRING,
179
+ }
180
+ ]],
181
+ NilClass
182
+ ],
183
+ "talks" => [:optional,
184
+ [[
185
+ {
186
+ "title" => String,
187
+ "venue" => String,
188
+ "date" => PARTIAL_DATE,
189
+ "url" => OPTIONAL_STRING,
190
+ }
191
+ ]],
192
+ NilClass
193
+ ],
194
+ "awards" => [:optional,
195
+ [[
196
+ {
197
+ "who" => String,
198
+ "address" => OPTIONAL_STRING,
199
+ "date" => PARTIAL_DATE,
200
+ "title" => String,
201
+ "summary" => OPTIONAL_STRING
202
+ }
203
+ ]],
204
+ NilClass
205
+ ],
206
+ "achievements" => [:optional,
207
+ [[
208
+ {
209
+ "who" => String,
210
+ "address" => OPTIONAL_STRING,
211
+ "date" => OPTIONAL_PARTIAL_DATE,
212
+ "title" => String,
213
+ "summary" => OPTIONAL_STRING
214
+ }
215
+ ]],
216
+ NilClass
217
+ ],
218
+ "software" => [:optional,
219
+ [[
220
+ {
221
+ "title" => String,
222
+ "url" => OPTIONAL_STRING,
223
+ "programming_language" => OPTIONAL_STRING,
224
+ "license" => OPTIONAL_STRING,
225
+ "role" => OPTIONAL_STRING,
226
+ "summary" => OPTIONAL_STRING,
227
+ }
228
+ ]],
229
+ NilClass
230
+ ],
231
+ "skills" => [:optional,
232
+ [[
233
+ {
234
+ "name" => String,
235
+ "level" => OPTIONAL_STRING,
236
+ "summary" => OPTIONAL_STRING,
237
+ }
238
+ ]],
239
+ NilClass
240
+ ],
241
+ "driving" => [:optional,
242
+ [[ { "license" => String, } ]],
243
+ NilClass
244
+ ],
245
+ "languages" => [:optional,
246
+ {
247
+ "mother_tongues" => [[
248
+ {
249
+ "code" => OPTIONAL_STRING,
250
+ "language" => String,
251
+ }
252
+ ]],
253
+ "foreign" => [:optional,
254
+ [[
255
+ {
256
+ "code" => OPTIONAL_STRING,
257
+ "language" => String,
258
+ "level" => OPTIONAL_STRING,
259
+ "listening" => OPTIONAL_STRING,
260
+ "reading" => OPTIONAL_STRING,
261
+ "spoken_interaction" => OPTIONAL_STRING,
262
+ "spoken_production" => OPTIONAL_STRING,
263
+ "writing" => OPTIONAL_STRING
264
+ }
265
+ ]],
266
+ NilClass
267
+ ]
268
+ },
269
+ NilClass
270
+ ],
271
+ "interests" => [:optional,
272
+ [[
273
+ {
274
+ "name" => String,
275
+ "level" => OPTIONAL_STRING,
276
+ "summary" => OPTIONAL_STRING,
277
+ }
278
+ ]],
279
+ NilClass
280
+ ],
281
+ "references" => [:optional,
282
+ [[
283
+ {
284
+ "name" => String,
285
+ "reference" => String,
286
+ "contacts" => [[ {"label" => String, "value" => String} ]]
287
+ }
288
+ ]],
289
+ NilClass
290
+ ]
291
+ }
292
+ end
293
+ end