resme 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +52 -30
- data/lib/resme/cli/command_semantics.rb +40 -1
- data/lib/resme/cli/command_syntax.rb +46 -4
- data/lib/resme/renderer/renderer.rb +74 -9
- data/lib/resme/templates/europass/eu.xml.erb +2 -1
- data/lib/resme/templates/resume.json.erb +27 -27
- data/lib/resme/templates/resume.md.erb +141 -99
- data/lib/resme/templates/resume.org.erb +202 -0
- data/lib/resme/templates/resume.yml +43 -39
- data/lib/resme/templates/schema.yml +494 -0
- data/lib/resme/version.rb +1 -1
- data/resme.gemspec +1 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88b2d8e225f97233ed991e95c8be2798b982d803941dacb7fd71b623a32e7822
|
4
|
+
data.tar.gz: 7f44a0bf98b3e49b33fce8924ae74545aa6591bfec53f1952aa9067167a8079c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e0bdffd6b716cc6056c158a1672144343a66674c5371136d70c302d6e338279e0e582f0fbd126e577967d6f668557537f6732102faad014ec3478d87c4e2cd9b
|
7
|
+
data.tar.gz: 8feff2a7f410655dffc1199c6b47d9358acbaf5f0450715608b95dc393ec0ab2a25fc53b87a3accac0e047cca05c7ac2e02c0e2f710051b4af7bcde44e46180c
|
data/README.md
CHANGED
@@ -1,22 +1,18 @@
|
|
1
1
|
# RESME - A Resume Generator
|
2
2
|
|
3
3
|
Keep your resume in YAML and output it in various formats, including
|
4
|
-
markdown, json, and the Europass XML format.
|
5
|
-
|
6
|
-
Interesting features:
|
7
|
-
|
8
|
-
- the rendering engine is based on ERB. This simplifies the creation
|
9
|
-
of new output formats (and extending/modifying the YML structure to
|
10
|
-
one's needs).
|
11
|
-
- no gem/cli application I am aware of outputs in the Europass format
|
4
|
+
org-mode, markdown, json, and the Europass XML format.
|
12
5
|
|
6
|
+
The rendering engine is based on ERB. This simplifies the creation of
|
7
|
+
new output formats (and extending/modifying the YML structure to
|
8
|
+
one's needs).
|
13
9
|
|
14
10
|
## Installation
|
15
11
|
|
16
12
|
Add this line to your application's Gemfile:
|
17
13
|
|
18
14
|
```ruby
|
19
|
-
gem 'resme'
|
15
|
+
gem 'resme'
|
20
16
|
```
|
21
17
|
|
22
18
|
And then execute:
|
@@ -36,11 +32,14 @@ Start with:
|
|
36
32
|
whih generates a YML template for your resume in the current
|
37
33
|
directory. Comments in the YML file should help you fill the various
|
38
34
|
entries. Notice that most entries are optional and you can remove
|
39
|
-
|
35
|
+
sections which are not relevant for your resume.
|
36
|
+
|
37
|
+
You can then generate a resume using one of the existing templates or
|
38
|
+
by writing your own template (see below).
|
40
39
|
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
To generate a resume in Markdown using the provided template:
|
41
|
+
|
42
|
+
$ resme org [-o output_filename] file.yml ...
|
44
43
|
|
45
44
|
To generate a resume in Markdown using the provided template:
|
46
45
|
|
@@ -50,7 +49,7 @@ To generate a resume in the Europass XML format using the provided template:
|
|
50
49
|
|
51
50
|
$ resme europass [-o output_filename] file.yml ...
|
52
51
|
|
53
|
-
To generate a resume in the
|
52
|
+
To generate a resume in the JSON format (https://jsonresume.org/):
|
54
53
|
|
55
54
|
$ resme json [-o output_filename] file.yaml ...
|
56
55
|
|
@@ -64,6 +63,15 @@ Remarks:
|
|
64
63
|
generated to `resume-YYYY.MM.DD.format`, where `YYYY-MM-DD` is today's date
|
65
64
|
and `format` is the chosen output format
|
66
65
|
|
66
|
+
## Checking validity
|
67
|
+
|
68
|
+
Use the `check` command to verify whether your YAML file conforms with
|
69
|
+
the intended syntax.
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
resme check resume.yaml
|
73
|
+
```
|
74
|
+
|
67
75
|
## Dates in the resume
|
68
76
|
|
69
77
|
Enter dates in the resume in one of the following formats:
|
@@ -79,9 +87,11 @@ irrelevant).
|
|
79
87
|
|
80
88
|
## Creating your own templates
|
81
89
|
|
82
|
-
The resumes are generated from the YML matter using ERB templates
|
83
|
-
|
84
|
-
|
90
|
+
The resumes are generated from the YML matter using ERB templates.
|
91
|
+
The output formats should support different backends (OrgMode and
|
92
|
+
Markdown easily allow for generation of PDFs, HTML, and ODT to mention
|
93
|
+
a few).
|
94
|
+
|
85
95
|
You can define your own templates if you wish to do so.
|
86
96
|
|
87
97
|
All the data in the resume is made available in the `data` variable.
|
@@ -104,12 +114,16 @@ Some functions can be used in the templates to better control the output.
|
|
104
114
|
String manipulation functions:
|
105
115
|
|
106
116
|
* `clean string` removes any space at the beginning of `string`
|
107
|
-
* `reflow string, n` makes `string` into an array of strings of
|
108
|
-
|
109
|
-
for instance.
|
117
|
+
* `reflow string, n` makes `string` into an array of strings of length
|
118
|
+
lower or equal to `n` (useful if you are outputting a textual
|
119
|
+
format, for instance.
|
110
120
|
|
111
121
|
Dates manipulation functions:
|
112
122
|
|
123
|
+
* `period` generates a string recapping a period. The function
|
124
|
+
abstracts different syntax you can use for entries (i.e., `date` or
|
125
|
+
`from` and `till`) and different values for the entries (e.g., a
|
126
|
+
missing value for `till`)
|
113
127
|
* `year string`, `month string`, `day string` return, respectively the
|
114
128
|
year, month and day from strings in the format `YYYY-MM-DD`s
|
115
129
|
* `has_month input` returns true if `input` has a month, that is, it is
|
@@ -117,7 +131,7 @@ Dates manipulation functions:
|
|
117
131
|
* `has_day input` returns true if `input` has a day, that is, it is
|
118
132
|
a date or it is in the form `YYYY-MM-DD`
|
119
133
|
|
120
|
-
You can find
|
134
|
+
You can find the templates in `lib/resme/templates`. These might be
|
121
135
|
good starting points if you want to develop your own.
|
122
136
|
|
123
137
|
## Contributing your templates
|
@@ -154,17 +168,25 @@ In `doc/todo.org` ... guess what is my preferred editor!
|
|
154
168
|
|
155
169
|
## Bugs
|
156
170
|
|
157
|
-
There are slight differences in the
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
171
|
+
There are still slight differences in the syntax of entries and in the
|
172
|
+
way in which the information is formatted in various output formats.
|
173
|
+
For instance, gender and birthdate are used in the Europass format,
|
174
|
+
but not in the Markdown format. This is in part due to the different
|
175
|
+
standards and in part due to personal preferences.
|
162
176
|
|
163
|
-
Entries are not sorted by date before outputting them. Make sure you
|
164
|
-
put them in the order you want them to appear in your resume
|
177
|
+
**Entries are not sorted by date before outputting them. Make sure you
|
178
|
+
put them in the order you want them to appear in your resume.**
|
165
179
|
|
166
|
-
|
180
|
+
Unknown number of unknown bugs.
|
167
181
|
|
168
182
|
## Release History
|
169
183
|
|
170
|
-
* **0.
|
184
|
+
* **0.3** introduces output to org-mode, introduces references for the
|
185
|
+
CV, improves output to JSON, adds a `check` command, removes useless
|
186
|
+
blank lines in the output, supports `-%>` in the ERB templates,
|
187
|
+
fixes various typos in the documentation, introduces various new
|
188
|
+
formatting functions, to simplify template generation
|
189
|
+
* **0.2** improves output of volunteering activities and other
|
190
|
+
information in the Europass and **significantly improves error and
|
191
|
+
warning reporting**
|
192
|
+
* **0.1** is the first release
|
@@ -4,6 +4,8 @@ require 'fileutils'
|
|
4
4
|
require 'date'
|
5
5
|
require 'yaml'
|
6
6
|
require 'erb'
|
7
|
+
require 'json'
|
8
|
+
require 'kwalify'
|
7
9
|
|
8
10
|
module Resme
|
9
11
|
module CommandSemantics
|
@@ -90,6 +92,32 @@ module Resme
|
|
90
92
|
#
|
91
93
|
# APP SPECIFIC COMMANDS
|
92
94
|
#
|
95
|
+
def self.check opts, argv
|
96
|
+
schema = Kwalify::Yaml.load_file(File.join(File.dirname(__FILE__), "/../templates/schema.yml"))
|
97
|
+
## or
|
98
|
+
# schema = YAML.load_file('schema.yaml')
|
99
|
+
|
100
|
+
## create validator
|
101
|
+
validator = Kwalify::Validator.new(schema)
|
102
|
+
|
103
|
+
## load document
|
104
|
+
document = Kwalify::Yaml.load_file(argv[0])
|
105
|
+
## or
|
106
|
+
#document = YAML.load_file('document.yaml')
|
107
|
+
|
108
|
+
## validate
|
109
|
+
errors = validator.validate(document)
|
110
|
+
|
111
|
+
## show errors
|
112
|
+
if errors && !errors.empty?
|
113
|
+
for e in errors
|
114
|
+
puts "[#{e.path}] #{e.message}"
|
115
|
+
end
|
116
|
+
else
|
117
|
+
puts "The file #{argv[0]} validates."
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
93
121
|
def self.init opts, argv
|
94
122
|
output = opts[:output] || "resume.yml"
|
95
123
|
force = opts[:force]
|
@@ -113,6 +141,14 @@ module Resme
|
|
113
141
|
puts "Resume generated in #{output}"
|
114
142
|
end
|
115
143
|
|
144
|
+
def self.org opts, argv
|
145
|
+
output = opts[:output] || "resume-#{Date.today}.org"
|
146
|
+
template = File.join(File.dirname(__FILE__), "/../templates/resume.org.erb")
|
147
|
+
|
148
|
+
render argv, template, output
|
149
|
+
puts "Resume generated in #{output}"
|
150
|
+
end
|
151
|
+
|
116
152
|
def self.json opts, argv
|
117
153
|
output = opts[:output] || "resume-#{Date.today}.json"
|
118
154
|
template = File.join(File.dirname(__FILE__), "/../templates/resume.json.erb")
|
@@ -138,7 +174,10 @@ module Resme
|
|
138
174
|
data = data.merge(YAML.load_file(file))
|
139
175
|
end
|
140
176
|
template = File.read(template_filename)
|
141
|
-
output = ERB.new(template).result(binding)
|
177
|
+
output = ERB.new(template, nil, '-').result(binding)
|
178
|
+
# it is difficult to write readable ERBs with no empty lines...
|
179
|
+
# we use gsub to replace multiple empty lines with \n\n in the final output
|
180
|
+
output.gsub!(/([\t ]*\n){3,}/, "\n\n")
|
142
181
|
backup_and_write output_filename, output
|
143
182
|
end
|
144
183
|
|
@@ -97,6 +97,26 @@ EOS
|
|
97
97
|
return { :help => [opts, :help, help] }
|
98
98
|
end
|
99
99
|
|
100
|
+
def self.check_opts
|
101
|
+
opts = Slop::Options.new
|
102
|
+
opts.banner = "check -- Check whether a YAML file conforms with the RESME syntax"
|
103
|
+
help = <<EOS
|
104
|
+
NAME
|
105
|
+
#{opts.banner}
|
106
|
+
|
107
|
+
SYNOPSYS
|
108
|
+
#{opts.to_s}
|
109
|
+
|
110
|
+
DESCRIPTION
|
111
|
+
Check whether a YAML file conforms with the RESME syntax.
|
112
|
+
|
113
|
+
|
114
|
+
EXAMPLES
|
115
|
+
resme file.yml
|
116
|
+
EOS
|
117
|
+
return { :check => [opts, :check, help] }
|
118
|
+
end
|
119
|
+
|
100
120
|
def self.init_opts
|
101
121
|
opts = Slop::Options.new
|
102
122
|
opts.banner = "init [-o output_filename]"
|
@@ -111,7 +131,7 @@ SYNOPSYS
|
|
111
131
|
|
112
132
|
DESCRIPTION
|
113
133
|
|
114
|
-
Generate a
|
134
|
+
Generate a YAML template for your resume in the current directory.
|
115
135
|
|
116
136
|
EXAMPLES
|
117
137
|
|
@@ -135,7 +155,7 @@ SYNOPSYS
|
|
135
155
|
|
136
156
|
DESCRIPTION
|
137
157
|
|
138
|
-
Generate a markdown resume from the
|
158
|
+
Generate a markdown resume from the YAML input files.
|
139
159
|
|
140
160
|
EXAMPLES
|
141
161
|
|
@@ -144,6 +164,28 @@ EOS
|
|
144
164
|
return { md: [opts, :md, help] }
|
145
165
|
end
|
146
166
|
|
167
|
+
def self.org_opts
|
168
|
+
opts = Slop::Options.new
|
169
|
+
opts.banner = "org [-o output_filename] file.yml ..."
|
170
|
+
opts.string "-o", "--output", "Output filename"
|
171
|
+
help = <<EOS
|
172
|
+
NAME
|
173
|
+
#{opts.banner}
|
174
|
+
|
175
|
+
SYNOPSYS
|
176
|
+
#{opts.to_s}
|
177
|
+
|
178
|
+
DESCRIPTION
|
179
|
+
|
180
|
+
Generate an org-mode resume from the YAML input files.
|
181
|
+
|
182
|
+
EXAMPLES
|
183
|
+
|
184
|
+
resme md -o r.md resume.yml
|
185
|
+
EOS
|
186
|
+
return { org: [opts, :org, help] }
|
187
|
+
end
|
188
|
+
|
147
189
|
def self.json_opts
|
148
190
|
opts = Slop::Options.new
|
149
191
|
opts.banner = "json [-o output_filename] file.yml ..."
|
@@ -157,7 +199,7 @@ SYNOPSYS
|
|
157
199
|
|
158
200
|
DESCRIPTION
|
159
201
|
|
160
|
-
Generate a JSON resume from the
|
202
|
+
Generate a JSON resume from the YAML input files.
|
161
203
|
|
162
204
|
EXAMPLES
|
163
205
|
|
@@ -179,7 +221,7 @@ SYNOPSYS
|
|
179
221
|
|
180
222
|
DESCRIPTION
|
181
223
|
|
182
|
-
Generate a Europass XML resume from the
|
224
|
+
Generate a Europass XML resume from the YAML input files.
|
183
225
|
|
184
226
|
EXAMPLES
|
185
227
|
|
@@ -1,14 +1,18 @@
|
|
1
1
|
#
|
2
2
|
# Utility functions you might want to use in your ERB
|
3
|
-
#
|
3
|
+
# templates
|
4
4
|
#
|
5
5
|
|
6
6
|
# remove spaces at the beginning of string
|
7
|
-
#
|
7
|
+
# replace extra spaces and special chars with a single space
|
8
8
|
def clean string
|
9
9
|
string.gsub(/^[\t\n ]+/, "").gsub(/[\t\n ]+/, " ")
|
10
10
|
end
|
11
11
|
|
12
|
+
def full_name data
|
13
|
+
[data.basics["first_name"], data.basics["middle_name"], data.basics["last_name"]].join(" ")
|
14
|
+
end
|
15
|
+
|
12
16
|
# break a string into substrings of length chars breaking at spaces
|
13
17
|
# and newlines
|
14
18
|
#
|
@@ -20,24 +24,86 @@ end
|
|
20
24
|
# special cases: if one line is longer than chars characters, then
|
21
25
|
# break at the first space after chars
|
22
26
|
#
|
23
|
-
def
|
27
|
+
def reflow_to_array string, chars
|
24
28
|
if string.length < chars
|
25
29
|
return [clean(string)]
|
26
30
|
else
|
27
31
|
chars.downto(0).each do |index|
|
28
32
|
if string[index] == " " or string[index] == "\n" or string[index] == "\t"
|
29
|
-
return [clean(string[0..index-1])] +
|
33
|
+
return [clean(string[0..index-1])] + reflow_to_array(string[index+1..-1], chars)
|
30
34
|
end
|
31
35
|
end
|
32
36
|
chars.upto(string.length).each do |index|
|
33
37
|
if string[index] == " " or string[index] == "\n" or string[index] == "\t"
|
34
|
-
return [clean(string[0..index-1])] +
|
38
|
+
return [clean(string[0..index-1])] + reflow_to_array(string[index+1..-1], chars)
|
35
39
|
end
|
36
40
|
end
|
37
41
|
return [clean(string)]
|
38
42
|
end
|
39
43
|
end
|
40
44
|
|
45
|
+
def reflow_to_string string, chars, indentation = ""
|
46
|
+
array = reflow_to_array string, chars
|
47
|
+
output = ""
|
48
|
+
array.each do |line|
|
49
|
+
output << indentation + line + "\n"
|
50
|
+
end
|
51
|
+
output
|
52
|
+
end
|
53
|
+
|
54
|
+
# manage dates of an entry flexibly supporting the following formats:
|
55
|
+
#
|
56
|
+
# - from - till (with `from` or `till` possibly partial or empty)
|
57
|
+
# - date (possibly partial)
|
58
|
+
#
|
59
|
+
# abstract dates at the year level, taking care of periods if from and
|
60
|
+
# till are in two different years
|
61
|
+
def period entry
|
62
|
+
if entry["date"] then
|
63
|
+
"#{year entry.date}"
|
64
|
+
else
|
65
|
+
from_year = entry["from"] ? year(entry.from.to_s) : nil
|
66
|
+
till_year = entry["till"] ? year(entry.till.to_s) : nil
|
67
|
+
|
68
|
+
if from_year and till_year and from_year == till_year then
|
69
|
+
from_year
|
70
|
+
else
|
71
|
+
"#{from_year} -- #{till_year ? till_year : "today"}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# make an entry into an item of a list
|
77
|
+
# - first argument, entry, is a hash containing the symbols specified in header and
|
78
|
+
# the following fields: summary and then from, till, or date
|
79
|
+
# - second argument, header, is an array of symbols, whose values, comma-separated will generate the
|
80
|
+
# header line
|
81
|
+
#
|
82
|
+
# The output is along the lines of:
|
83
|
+
#
|
84
|
+
# - value of key1, value of key2
|
85
|
+
# period
|
86
|
+
# reflowed summary
|
87
|
+
def itemize entry, header = ["role", "who", "address"]
|
88
|
+
<<EOS
|
89
|
+
- #{clean header.map { |x| entry[x] }.join(", ")}
|
90
|
+
#{period entry}
|
91
|
+
#{reflow_to_string entry["summary"], 72, " "}
|
92
|
+
EOS
|
93
|
+
end
|
94
|
+
|
95
|
+
# generate a list of org-mode properties from item and exclude summary from the list
|
96
|
+
def propertify item, indentation=""
|
97
|
+
output = ":PROPERTIES:\n"
|
98
|
+
item.each do |k, v|
|
99
|
+
if not ["summary", "details"].include? k
|
100
|
+
output << ":#{k.upcase}: #{v}\n"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
output << ":END:"
|
104
|
+
output.gsub!(/^/, indentation)
|
105
|
+
end
|
106
|
+
|
41
107
|
# Utility function for managing dates (2015-01-01) and partial dates (2015-05)
|
42
108
|
def year string
|
43
109
|
string.to_s[0..3]
|
@@ -75,7 +141,7 @@ class Hash
|
|
75
141
|
|
76
142
|
# error: nil value
|
77
143
|
if self.has_key? key and self[key] == nil
|
78
|
-
$stderr.puts "
|
144
|
+
$stderr.puts "[W] The value of key '#{key}' is nil in the following entry:"
|
79
145
|
|
80
146
|
# we put a bit of info about the top level structure of a resume to avoid extra-long error messages
|
81
147
|
# I don't want to print detailed information about top-level entries missing in the resume
|
@@ -84,10 +150,9 @@ class Hash
|
|
84
150
|
"committees", "volunteer", "visits", "education", "publications", "talks", "awards", "achievements",
|
85
151
|
"software", "skills", "languages", "driving", "interests", "references"]
|
86
152
|
if not top_level_entries.include?(key) then
|
87
|
-
$stderr.puts "Offending entry:"
|
88
153
|
# $stderr.puts self.to_s
|
89
154
|
self.keys.each do |k|
|
90
|
-
$stderr.puts "
|
155
|
+
$stderr.puts " #{k}: #{self[k]}"
|
91
156
|
end
|
92
157
|
$stderr.puts ""
|
93
158
|
end
|
@@ -100,7 +165,7 @@ class Hash
|
|
100
165
|
# the actual mileage might vary
|
101
166
|
|
102
167
|
# more error reporting: key not found
|
103
|
-
$stderr.puts "
|
168
|
+
$stderr.puts "[E] Key '#{key}' not found in the following entry:"
|
104
169
|
# $stderr.puts self.to_s
|
105
170
|
self.keys.each do |k|
|
106
171
|
$stderr.puts " #{k}: #{self[k]}"
|