resme 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.org +19 -0
- data/README.org +49 -22
- data/lib/resme/cli/command_semantics.rb +22 -17
- data/lib/resme/cli/command_syntax.rb +29 -0
- data/lib/resme/cli/resume_structure_validator.rb +293 -0
- data/lib/resme/renderer/renderer.rb +6 -52
- data/lib/resme/templates/resume.json.erb +148 -130
- data/lib/resme/templates/resume.md.erb +36 -36
- data/lib/resme/templates/resume.org.erb +45 -45
- data/lib/resme/templates/resume.xml.erb +108 -107
- data/lib/resme/version.rb +1 -1
- data/lib/resme.rb +1 -0
- data/resme.gemspec +5 -7
- metadata +11 -24
- data/lib/resme/templates/schema.yml +0 -494
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27d07b29ff7369747e8f43ad9cbdc29efe1023cca20de554f94c02fdc33c57f8
|
4
|
+
data.tar.gz: fc048d5417183cba079e387caa3c08d2540fb937a384aae05759622b91b82846
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77bab2cbc35e21bb7ce669100d6b0f66d4b9f60e496814b35aec3a1fb3346730bbfe0700d884cde697500a479007cbac61dc834dcd358b3b1ab4375e59ee60db
|
7
|
+
data.tar.gz: fa2c84c4358a0c86469f83f11cb8c361a8a90602ed58eadf8cf53701757d4525d1f27f27599fdf361af46a2fcce0243c2e014395375c84a82489834be5b56118
|
data/CHANGELOG.org
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#+TITLE: CHANGELOG
|
2
|
+
#+AUTHOR: Adolfo Villafiorita
|
3
|
+
|
4
|
+
* Version 1.5.0
|
5
|
+
- [user] New command =view= allows to view the template used
|
6
|
+
for generating a resume in a specific format
|
7
|
+
- [user] Check command is now based on ClassyHash
|
8
|
+
- [code] Various changes to the code
|
9
|
+
|
10
|
+
* Version 1.4.0
|
11
|
+
- [user] New option =--skip= allows to skip some top-level sections.
|
12
|
+
You mileage might vary, as some formats might require the
|
13
|
+
information you are trying to skip
|
14
|
+
- [user] New command =generate= is now used to generate the Resume
|
15
|
+
in different formats.
|
16
|
+
- [user] New option =--erb= allows to specify a custom template for
|
17
|
+
generating the resume. Use it in conjunction with =view= (released
|
18
|
+
in version 1.5.0) to jump-start your layout.
|
19
|
+
|
data/README.org
CHANGED
@@ -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).
|
68
|
-
renderer.
|
67
|
+
your own (see below).
|
69
68
|
|
70
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
+
view --template FORMAT # Print template used for format
|
85
|
+
console # enter the console
|
86
|
+
man # print resme manual page
|
87
|
+
help [command] # print command usage
|
88
|
+
init [options] # generate an empty resume.yml file
|
89
|
+
check resume.yml # Check syntax of resume.yml
|
90
|
+
list resume.yml # List main sections in resume.yml
|
91
|
+
version # print version information
|
92
|
+
generate [options] resume.yml ... # output resume
|
93
|
+
#+end_example
|
79
94
|
|
80
95
|
** Checking validity
|
81
96
|
:PROPERTIES:
|
@@ -110,24 +125,32 @@ 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
|
114
|
-
and Markdown easily allow for generation of PDFs, HTML,
|
115
|
-
mention a few
|
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.
|
116
133
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
134
|
+
Use the command =view= to print one of the templates used by =resme=
|
135
|
+
and build from that.
|
136
|
+
|
137
|
+
#+begin_example
|
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:
|
121
144
|
|
122
145
|
#+BEGIN_EXAMPLE
|
123
|
-
<% data
|
124
|
-
- <%= exp
|
125
|
-
From: <%= exp
|
146
|
+
<% data["work"] each do |exp| %>
|
147
|
+
- <%= exp["who"] %>
|
148
|
+
From: <%= exp["from"] %> till: <%= exp["till"] %>
|
126
149
|
<% end %>
|
127
150
|
#+END_EXAMPLE
|
128
151
|
|
129
|
-
To specify your own ERB template use the option
|
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
155
|
#+BEGIN_EXAMPLE
|
133
156
|
$ resme render -e template.md.erb [-o output_filename] file.yaml ...
|
@@ -200,6 +223,10 @@ 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 =doc/changelog.org=
|
229
|
+
|
203
230
|
** Roadmap
|
204
231
|
:PROPERTIES:
|
205
232
|
:CUSTOM_ID: roadmap
|
@@ -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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
puts "
|
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
|
-
|
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
|
@@ -10,7 +10,7 @@ def clean string
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def full_name data
|
13
|
-
[data
|
13
|
+
[data["basics"]["first_name"], data["basics"]["middle_name"], data["basics"]["last_name"]].join(" ")
|
14
14
|
end
|
15
15
|
|
16
16
|
# break a string into substrings of length chars breaking at spaces
|
@@ -59,13 +59,13 @@ end
|
|
59
59
|
# abstract dates at the year level, taking care of periods if from and
|
60
60
|
# till are in two different years
|
61
61
|
def period entry
|
62
|
-
if entry["date"]
|
63
|
-
"#{year entry
|
62
|
+
if entry["date"]
|
63
|
+
"#{year entry["date"]}"
|
64
64
|
else
|
65
|
-
from_year = entry["from"] ? year(entry
|
66
|
-
till_year = entry["till"] ? year(entry
|
65
|
+
from_year = entry["from"] ? year(entry["from"].to_s) : nil
|
66
|
+
till_year = entry["till"] ? year(entry["till"].to_s) : nil
|
67
67
|
|
68
|
-
if from_year
|
68
|
+
if from_year && till_year && from_year == till_year
|
69
69
|
from_year
|
70
70
|
else
|
71
71
|
"#{from_year} -- #{till_year ? till_year : "today"}"
|
@@ -132,49 +132,3 @@ def has_day input
|
|
132
132
|
input.size == 10
|
133
133
|
end
|
134
134
|
end
|
135
|
-
|
136
|
-
# Access hash keys like they were class methods (hash["key"] -> hash.key) and
|
137
|
-
# report errors if key is missing
|
138
|
-
class Hash
|
139
|
-
def method_missing(m)
|
140
|
-
key = m.to_s
|
141
|
-
|
142
|
-
# we put a bit of info about the top level structure of a resume to avoid
|
143
|
-
# extra-long error messages I don't want to print detailed information
|
144
|
-
# about top-level entries missing in the resume
|
145
|
-
top_level_entries = %w[
|
146
|
-
contacts addresses web_presence summary work teaching projects other
|
147
|
-
committees volunteer visits education publications talks awards
|
148
|
-
achievements software skills languages driving interests references
|
149
|
-
]
|
150
|
-
|
151
|
-
# error: nil value
|
152
|
-
if self.has_key? key and self[key] == nil
|
153
|
-
$stderr.puts "[W] The value of key '#{key}' is nil in the following entry:"
|
154
|
-
|
155
|
-
unless top_level_entries.include?(key)
|
156
|
-
# $stderr.puts self.to_s
|
157
|
-
self.keys.each do |k|
|
158
|
-
$stderr.puts " #{k}: #{self[k]}"
|
159
|
-
end
|
160
|
-
$stderr.puts ""
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
return self[key] if self.has_key? key
|
165
|
-
|
166
|
-
# we get here if the key is not found we report an error, return
|
167
|
-
# "" and continue the actual mileage might vary
|
168
|
-
|
169
|
-
$stderr.puts "[E] Key '#{key}' not found in the following entry:"
|
170
|
-
unless top_level_entries.include?(key)
|
171
|
-
self.keys.each do |k|
|
172
|
-
$stderr.puts " #{k}: #{self[k]}"
|
173
|
-
end
|
174
|
-
$stderr.puts ""
|
175
|
-
end
|
176
|
-
|
177
|
-
return ""
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|