resme 0.3.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.basics["first_name"], data.basics["middle_name"], data.basics["last_name"]].join(" ")
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"] then
63
- "#{year entry.date}"
62
+ if entry["date"]
63
+ "#{year entry["date"]}"
64
64
  else
65
- from_year = entry["from"] ? year(entry.from.to_s) : nil
66
- till_year = entry["till"] ? year(entry.till.to_s) : nil
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 and till_year and from_year == till_year then
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,47 +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
137
- # hash["key"] -> hash.key
138
- class Hash
139
- def method_missing(m)
140
- key = m.to_s
141
-
142
- # error: nil value
143
- if self.has_key? key and self[key] == nil
144
- $stderr.puts "[W] The value of key '#{key}' is nil in the following entry:"
145
-
146
- # we put a bit of info about the top level structure of a resume to avoid extra-long error messages
147
- # I don't want to print detailed information about top-level entries missing in the resume
148
- top_level_entries = [
149
- "contacts", "addresses", "web_presence", "summary", "work", "teaching", "projects", "other",
150
- "committees", "volunteer", "visits", "education", "publications", "talks", "awards", "achievements",
151
- "software", "skills", "languages", "driving", "interests", "references"]
152
- if not top_level_entries.include?(key) then
153
- # $stderr.puts self.to_s
154
- self.keys.each do |k|
155
- $stderr.puts " #{k}: #{self[k]}"
156
- end
157
- $stderr.puts ""
158
- end
159
- end
160
-
161
- return self[key] if self.has_key? key
162
-
163
- # we get here if the key is not found
164
- # we report an error, return "" and continue
165
- # the actual mileage might vary
166
-
167
- # more error reporting: key not found
168
- $stderr.puts "[E] Key '#{key}' not found in the following entry:"
169
- # $stderr.puts self.to_s
170
- self.keys.each do |k|
171
- $stderr.puts " #{k}: #{self[k]}"
172
- end
173
- $stderr.puts ""
174
-
175
- return ""
176
- end
177
- end
178
-
@@ -1,137 +1,155 @@
1
1
  {
2
- "basics": {
3
- "name": "<%= full_name(data).to_json %>",
4
- "label": "<%= data.basics.title.to_json %>",
5
- "picture": "<%= data.basics.picture %>",
6
- "email": "<%= e = data.contacts.select { |x| x.label == "email" }.first; e.value if e %>",
7
- "phone": "<%= p = data.contacts.select { |x| x.label == "mobile" }.first; p.value if p %>",
8
- "website": "<%= w = data.web_presence.select { |x| x.label == "website" }.first; w.value if w %>",
9
- "summary": "<%= data.summary.to_json %>",
10
- "location": {
11
- "address": "<%= data.addresses[0].street.to_json %>",
12
- "postalCode": "<%= data.addresses[0].zip_code %>",
13
- "city": "<%= data.addresses[0].city %>",
14
- "countryCode": "<%= data.addresses[0].country %>",
15
- "region": "<%= data.addresses[0].region %>"
16
- },
17
- "profiles": [
18
- <% sequence = data.web_presence || []; sequence.each_with_index do |profile, index| %>
19
- {
20
- "network": "<%= profile.label %>",
21
- <%# "username": "", %>
22
- "url": "<%= profile.value %>"
23
- }
24
- <%= "," unless index == sequence.size - 1 %>
25
- <% end %>
26
- ]
27
- },
28
- "work": [
29
- <% sequence = data.work || []; sequence.each_with_index do |job, index| %>
30
- {
31
- "company": "<%= job.who.to_json %>",
32
- "position": "<%= job.role.to_json %>",
33
- "website": "<%= job.website %>",
34
- "startDate": "<%= job.from %>",
35
- "endDate": "<%= job.till %>",
36
- "summary": "<%= job.summary.to_json %>"
37
- <%# "highlights": [ "Started the company" ] %>
38
- }
39
- <%= "," unless index == sequence.size - 1 %>
40
- <% end %>
41
- ],
42
- "volunteer": [
43
- <% sequence = data.volunteer || []; sequence.each_with_index do |job, index| %>
44
- {
45
- "organization": "<%= job.who.to_json %>",
46
- "position": "<%= job.role.to_json %>",
47
- "website": "<%= job.website %>",
48
- "startDate": "<%= job.from %>",
49
- "endDate": "<%= job.till %>",
50
- "summary": "<%= job.summary.to_json %>"
51
- <%# "highlights": [ "Started the company" ] %>
52
- }
53
- <%= "," unless index == sequence.size - 1 %>
54
- <% end %>
55
- ],
56
- "education": [
57
- <% sequence = data.education.select { |x| x.publish }; sequence.each_with_index do |edu, index| %>
2
+ "basics": {
3
+ "name": <%= full_name(data).to_json %>,
4
+ "label": <%= data["basics"]["title"].to_json %>,
5
+ <% if data["basics"]["picture"] -%>
6
+ "picture": <%= data["basics"]["picture"] %>,
7
+ <% end -%>
8
+ <% if data["contacts"] -%>
9
+ "email": "<%= ((data["contacts"] || []).select { |x| x["label"] == "email" }.first || {})["value"] %>",
10
+ "phone": "<%= ((data["contacts"] || []).select { |x| x["label"] == "mobile" }.first || {})["value"] %>",
11
+ <% end %>
12
+ <% if data["web_presence"] -%>
13
+ "website": "<%= ((data["web_presence"] || []).select { |x| x["label"] == "website" }.first || {})["value"] %>",
14
+ <% end -%>
15
+ <% if data["summary"] -%>
16
+ "summary": <%= data["summary"].to_json %>,
17
+ <% end -%>
18
+ <% if data["addresses"] -%>
19
+ "location": {
20
+ "address": <%= data["addresses"][0]["street"].to_json %>,
21
+ "postalCode": "<%= data["addresses"][0]["zip_code"] %>",
22
+ "city": "<%= data["addresses"][0]["city"] %>",
23
+ "countryCode": "<%= data["addresses"][0]["country"] %>",
24
+ "region": "<%= data["addresses"][0]["region"] %>"
25
+ },
26
+ <% end -%>
27
+ <% if data["web_presence"] -%>
28
+ "profiles": [
29
+ <% (data["web_presence"] || []).each do |profile| -%>
30
+ {
31
+ "network": "<%= profile["label"] %>",
32
+ "url": "<%= profile["value"] %>"
33
+ },
34
+ <% end -%>
35
+ ]
36
+ <% end -%>
37
+ },
38
+ <% if data["work"] -%>
39
+ "work": [
40
+ <% (data["work"] || []).each do |job| -%>
58
41
  {
59
- "institution": "<%= edu.school.to_json %>",
60
- "area": "<%= edu.topic.to_json %>",
61
- "studyType": "<%= edu.degree.to_json %>",
62
- "startDate": "<%= edu.from %>",
63
- "endDate": "<%= edu.till %>",
64
- "gpa": "<%= edu.score %>"
65
- <%# "courses": [ "DB1101 - Basic SQL" ] %>
66
- }
67
- <%= "," unless index == sequence.size - 1 %>
68
- <% end %>
69
- ],
70
- "awards": [
71
- <% sequence = data.awards || []; sequence.each_with_index do |award, index| %>
72
- {
73
- "title": "<%= award.title.to_json %>",
74
- "date": "<%= award.date %>",
75
- "awarder": "<%= award.who.to_json %>"
76
- "summary": "<%= award.summary.to_json %>"
77
- }
78
- <%= "," unless index == sequence.size - 1 %>
79
- <% end %>
80
- ],
81
- "publications": [
82
- <% squence = data.publications || []; sequence.each_with_index do |pub, index| %>
83
- {
84
- "name": "<%= pub.title.to_json %>",
85
- "publisher": "<%= pub.publisher.to_json %>",
86
- "releaseDate": "<%= pub.date %>",
87
- "website": "<%= pub.url %>",
88
- "summary": "<%= pub.summary.to_json %>"
89
- }
90
- <%= "," unless index == sequence.size - 1 %>
91
- <% end %>
92
- ],
93
- "skills": [
94
- <% sequence = data.skills || []; sequence.each_with_index do |skill, index| %>
95
- {
96
- "name": "<%= skill.name.to_json %>",
97
- "level": "<%= skill.level.to_json %>"
98
- <%# "keywords": [...] %>
99
- }
100
- <%= "," unless index == sequence.size - 1 %>
101
- <% end %>
102
- ],
103
- "languages": [
104
- <% data.languages.mother_tongues.each do |mt| %>
42
+ "company": <%= job["who"].to_json %>,
43
+ "position": <%= job["role"].to_json %>,
44
+ "website": "<%= job["website"] %>",
45
+ "startDate": "<%= job["from"] %>",
46
+ "endDate": "<%= job["till"] %>",
47
+ "summary": <%= job["summary"].to_json %>
48
+ <%# "highlights": [ "Started the company" ] %>
49
+ },
50
+ <% end -%>
51
+ ],
52
+ <% end -%>
53
+ <% if data["volunteer"] -%>
54
+ "volunteer": [
55
+ <% (data["volunteer"] || []).each do |job| -%>
105
56
  {
106
- "name": "<%= mt.language.to_json %>",
107
- "level": "Native speaker"
108
- }
109
- <%= "," unless data.languages.foreign == nil %>
110
- <% end %>
111
- <% sequence = data.languages.foreign || []; sequence.each_with_index do |lang, index| %>
57
+ "organization": <%= job["who"].to_json %>,
58
+ "position": <%= job["role"].to_json %>,
59
+ "website": "<%= job["website"] %>",
60
+ "startDate": "<%= job["from"] %>",
61
+ "endDate": "<%= job["till"] %>",
62
+ "summary": <%= job["summary"].to_json %>
63
+ <%# "highlights": [ "Started the company" ] %>
64
+ },
65
+ <% end -%>
66
+ ],
67
+ <% end %>
68
+ <% if data["education"] %>
69
+ "education": [
70
+ <% data["education"].select { |x| x["publish"] }.each do |edu| -%>
71
+ {
72
+ "institution": <%= edu["school"].to_json %>,
73
+ "area": <%= edu["topic"].to_json %>,
74
+ "studyType": <%= edu["degree"].to_json %>,
75
+ "startDate": "<%= edu["from"] %>",
76
+ "endDate": "<%= edu["till"] %>",
77
+ "gpa": "<%= edu["score"] %>"
78
+ <%# "courses": [ "DB1101 - Basic SQL" ] %>
79
+ },
80
+ <% end -%>
81
+ ],
82
+ <% end -%>
83
+ <% if data["awards"] -%>
84
+ "awards": [
85
+ <% (data["awards"] || []).each do |award| -%>
112
86
  {
113
- "name": "<%= lang.language.to_json %>",
114
- "level": "<%= lang.level.to_json %>"
115
- }
116
- <%= "," unless index == sequence.size - 1 %>
117
- <% end %>
118
- ],
119
- "interests": [
120
- <% sequence = data.interests || []; sequence.each_with_index do |interest, index| %>
87
+ "title": <%= award["title"].to_json %>,
88
+ "date": <%= award["date"] %>,
89
+ "awarder": <%= award["who"].to_json %>,
90
+ "summary": <%= award["summary"].to_json %>
91
+ },
92
+ <% end -%>
93
+ ],
94
+ <% end -%>
95
+ <% if data["publications"] -%>
96
+ "publications": [
97
+ <% (data["publications"] || []).each do |pub| -%>
121
98
  {
122
- "name": "<%= interest.name.to_json %>",
123
- <%# "keywords": [...] %>
99
+ "name": <%= pub["title"].to_json %>,
100
+ "publisher": <%= pub["publisher"].to_json %>,
101
+ "releaseDate": "<%= pub["date"] %>",
102
+ "website": "<%= pub["url"] %>",
103
+ "summary": <%= pub["summary"].to_json %>
124
104
  },
125
- <%= "," unless index == sequence.size - 1 %>
126
- <% end %>
127
- ],
128
- "references": [
129
- <% sequence = data.references || []; sequence.each_with_index do |reference, index| %>
130
- {
131
- "name": "<%= reference.name.to_json %>",
132
- "reference": "<%= reference.reference.to_json %>"
133
- }
134
- <%= "," unless index == sequence.size - 1 %>
135
- <% end %>
136
- ]
105
+ <% end -%>
106
+ ],
107
+ <% end -%>
108
+ <% if data["skills"] -%>
109
+ "skills": [
110
+ <% (data["skills"] || []).each do |skill| -%>
111
+ {
112
+ "name": <%= skill["name"].to_json %>,
113
+ "level": <%= skill["level"].to_json %>
114
+ <%# "keywords": [...] -%>
115
+ },
116
+ <% end -%>
117
+ ],
118
+ <% end -%>
119
+ <% if data["languages"] -%>
120
+ "languages": [
121
+ <% data["languages"]["mother_tongues"].each do |mt| -%>
122
+ {
123
+ "name": <%= mt["language"].to_json %>,
124
+ "level": "Native speaker"
125
+ },
126
+ <% end -%>
127
+ <% (data["languages"]["foreign"] || []).each do |lang| -%>
128
+ {
129
+ "name": <%= lang["language"].to_json %>,
130
+ "level": <%= lang["level"].to_json %>
131
+ },
132
+ <% end -%>
133
+ ],
134
+ <% end -%>
135
+ <% if data["interests"] -%>
136
+ "interests": [
137
+ <% (data["interests"] || []).each do |interest| -%>
138
+ {
139
+ "name": <%= interest["name"].to_json %>,
140
+ <%# "keywords": [...] -%>
141
+ },
142
+ <% end -%>
143
+ ],
144
+ <% end -%>
145
+ <% if data["references"] -%>
146
+ "references": [
147
+ <% (data["references"] || []).each do |reference| -%>
148
+ {
149
+ "name": <%= reference["name"].to_json %>,
150
+ "reference": <%= reference["reference"].to_json %>
151
+ },
152
+ <% end -%>
153
+ ]
154
+ <% end %>
137
155
  }