resme 0.3.1 → 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.
@@ -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
  }