resme 0.2.0 → 0.3.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/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]}"
|