resme 0.3.2 → 0.4.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.org +32 -35
- data/exe/resme +2 -2
- data/lib/resme/cli/command_semantics.rb +96 -94
- data/lib/resme/cli/command_syntax.rb +223 -204
- data/lib/resme/renderer/renderer.rb +20 -18
- data/lib/resme/templates/resume.md.erb +3 -3
- data/lib/resme/templates/resume.org.erb +9 -7
- data/lib/resme/templates/resume.xml.erb +495 -0
- data/lib/resme/version.rb +1 -1
- data/resme.gemspec +2 -3
- metadata +6 -20
- data/lib/resme/templates/europass/eu.xml.erb +0 -491
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8abd7d17f0d2b9a777cba680826c21d6ee882f3f3df0c74ba086e1d617d4b6b
|
4
|
+
data.tar.gz: b34b33c2f2d0f3beae3bef713efba2e39b05537d8a208380e6392271e0cbcb09
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe74aa311a4dd110c26a0760cf6d2f8cb1ba1ee8e587f2a64d00ff6aa420a2a7f6c37b1eec392ddf1b28abbe49ffb94d6b26d5f43f2797ba9fd7cc9874ebf5c1
|
7
|
+
data.tar.gz: 3f2ae60c5c8a4e7f8135aa7f43aafa081f913dc8dd6c1b4c6065606447a41b6afe414ae4f32e0541fc06284227d94628f31f59e80af02aee2147c054032137c8
|
data/README.org
CHANGED
@@ -3,13 +3,8 @@
|
|
3
3
|
#+DATE: <2020-07-14 Tue>
|
4
4
|
#+STARTUP: showall
|
5
5
|
|
6
|
-
* RESME - A Resume Generator
|
7
|
-
:PROPERTIES:
|
8
|
-
:CUSTOM_ID: resme---a-resume-generator
|
9
|
-
:END:
|
10
|
-
|
11
6
|
Keep your resume in YAML and output it in various formats, including
|
12
|
-
|
7
|
+
Org Mode, Markdown, JSON, and the Europass XML format.
|
13
8
|
|
14
9
|
The rendering engine is based on ERB. This simplifies the creation of
|
15
10
|
new output formats (and extending/modifying the YML structure to one's
|
@@ -46,41 +41,39 @@ Or install it yourself as:
|
|
46
41
|
Start with:
|
47
42
|
|
48
43
|
#+BEGIN_EXAMPLE
|
49
|
-
|
44
|
+
resme init
|
50
45
|
#+END_EXAMPLE
|
51
46
|
|
52
|
-
|
47
|
+
which generates a YML template for your resume in the current directory.
|
53
48
|
Comments in the YML file should help you fill the various entries.
|
54
49
|
Notice that most entries are optional and you can remove sections which
|
55
50
|
are not relevant for your resume.
|
56
51
|
|
57
|
-
You can
|
58
|
-
writing your own template (see below).
|
59
|
-
|
60
|
-
To generate a resume in Markdown using the provided template:
|
61
|
-
|
62
|
-
$ resme org [-o output_filename] file.yml ...
|
52
|
+
You can now generate a resume using one of the existing formats:
|
63
53
|
|
64
|
-
|
54
|
+
#+begin_example
|
55
|
+
resme --to org resume.yml
|
56
|
+
#+end_example
|
65
57
|
|
66
|
-
|
58
|
+
Supported formats include:
|
67
59
|
|
68
|
-
|
69
|
-
|
60
|
+
- =org=: Org Mode format
|
61
|
+
- =md=: Markdown format
|
62
|
+
- =europass=: Europass format
|
63
|
+
(http://interop.europass.cedefop.europa.eu/web-services/remote-upload/)
|
64
|
+
- =json=: JSON format (https://jsonresume.org/)
|
70
65
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
$ resme json [-o output_filename] file.yaml ...
|
66
|
+
If you are not satisfied with the provided templates, you can write
|
67
|
+
your own (see below). In this case, however, =resme= is mainly an ERB
|
68
|
+
renderer.
|
76
69
|
|
77
70
|
Remarks:
|
78
71
|
|
79
|
-
-
|
72
|
+
- You can specify more than one YML file in input. This allows you to
|
80
73
|
store data about your resume in different files, if you like to do so
|
81
74
|
(e.g., work experiences could be in one file and talks in another).
|
82
75
|
The YML files are merged before processing them.
|
83
|
-
-
|
76
|
+
- The output filename is optional. If you do not specify one, the resume
|
84
77
|
is generated to =resume-YYYY.MM.DD.format=, where =YYYY-MM-DD= is
|
85
78
|
today's date and =format= is the chosen output format
|
86
79
|
|
@@ -117,14 +110,14 @@ The third and the forth format allows you to enter "partial" dates
|
|
117
110
|
:END:
|
118
111
|
|
119
112
|
The resumes are generated from the YML matter using ERB templates. The
|
120
|
-
output formats should support different
|
121
|
-
easily allow for generation of PDFs, HTML, and ODT to
|
122
|
-
|
123
|
-
You can define your own templates if you wish to do so.
|
113
|
+
provided output formats should support different back ends (Org Mode
|
114
|
+
and Markdown easily allow for generation of PDFs, HTML, and ODT, to
|
115
|
+
mention a few).
|
124
116
|
|
125
|
-
|
126
|
-
|
127
|
-
the work
|
117
|
+
However, if you want, you can define your own templates. All the data
|
118
|
+
in the resume is made available in the =data= variable. Thus, for
|
119
|
+
instance, the following code snippets generates a list of all the work
|
120
|
+
experiences:
|
128
121
|
|
129
122
|
#+BEGIN_EXAMPLE
|
130
123
|
<% data.work each do |exp| %>
|
@@ -133,11 +126,11 @@ the work experiences:
|
|
133
126
|
<% end %>
|
134
127
|
#+END_EXAMPLE
|
135
128
|
|
136
|
-
To specify your own ERB template use the option =-
|
137
|
-
instance:
|
129
|
+
To specify your own ERB template use the option =-e= (=--erb=). Thus,
|
130
|
+
for instance:
|
138
131
|
|
139
132
|
#+BEGIN_EXAMPLE
|
140
|
-
$ resme render -
|
133
|
+
$ resme render -e template.md.erb [-o output_filename] file.yaml ...
|
141
134
|
#+END_EXAMPLE
|
142
135
|
|
143
136
|
uses =template.md.erb= to generate the resume.
|
@@ -235,6 +228,10 @@ Unknown number of unknown bugs.
|
|
235
228
|
:CUSTOM_ID: release-history
|
236
229
|
:END:
|
237
230
|
|
231
|
+
- *0.4.0* refactors all generation commands under =generate=, provides
|
232
|
+
new filtering options, adds =-e= option (for custom templates), and
|
233
|
+
refactors various portions of code. It also revises this document
|
234
|
+
and fixes some minor bugs.
|
238
235
|
- *0.3.2* and *0.3.1* fix errors with the Europass format: lists of
|
239
236
|
projects, interests, ... are now properly formatted.
|
240
237
|
- *0.3* introduces output to org-mode, introduces references for the CV,
|
data/exe/resme
CHANGED
@@ -1,90 +1,90 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
1
|
+
require "resme/version"
|
2
|
+
require "readline"
|
3
|
+
require "fileutils"
|
4
|
+
require "date"
|
5
|
+
require "yaml"
|
6
|
+
require "erb"
|
7
|
+
require "json"
|
8
|
+
require "kwalify"
|
9
9
|
|
10
10
|
module Resme
|
11
11
|
module CommandSemantics
|
12
|
-
APPNAME =
|
12
|
+
APPNAME = "resme"
|
13
13
|
VERSION = Resme::VERSION
|
14
14
|
|
15
15
|
#
|
16
16
|
# Main App Starts Here!
|
17
17
|
#
|
18
|
-
def self.version
|
18
|
+
def self.version(opts = nil, argv = [])
|
19
19
|
puts "#{APPNAME} version #{VERSION}"
|
20
20
|
end
|
21
21
|
|
22
|
-
def self.man
|
23
|
-
path = File.join(File.dirname(__FILE__), "/../../../README.
|
22
|
+
def self.man(opts = nil, argv = [])
|
23
|
+
path = File.join(File.dirname(__FILE__), "/../../../README.org")
|
24
24
|
file = File.open(path, "r")
|
25
|
-
|
26
|
-
puts contents
|
25
|
+
puts file.read
|
27
26
|
end
|
28
27
|
|
29
|
-
def self.help
|
28
|
+
def self.help(opts = nil, argv = [])
|
30
29
|
all_commands = CommandSyntax.commands
|
31
30
|
|
32
31
|
if argv != []
|
33
|
-
argv.map
|
32
|
+
argv.map do |x|
|
33
|
+
puts all_commands[x.to_sym][:help]
|
34
|
+
puts "\n\n"
|
35
|
+
end
|
34
36
|
else
|
35
|
-
puts "#{APPNAME} command [options] [args]"
|
36
|
-
puts ""
|
37
|
-
|
38
|
-
|
39
|
-
all_commands.keys.each do |key|
|
40
|
-
puts " " + all_commands[key][0].banner
|
37
|
+
puts "#{APPNAME} command [options] [args]\n"
|
38
|
+
puts "Available commands:\n"
|
39
|
+
all_commands.each_key do |key|
|
40
|
+
puts " #{all_commands[key][:options].banner}"
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
def self.console
|
45
|
+
def self.console(opts, argv = [])
|
46
46
|
all_commands = CommandSyntax.commands
|
47
47
|
all_commands.delete(:console)
|
48
48
|
|
49
49
|
i = 0
|
50
50
|
while true
|
51
51
|
string = Readline.readline("#{APPNAME}:%03d> " % i, true)
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
reps all_commands, string.split(' ')
|
52
|
+
# as a courtesy, remove any leading appname string
|
53
|
+
string.gsub!(/^#{APPNAME} /, "")
|
54
|
+
exit 0 if %w[exit quit .].include? string
|
55
|
+
execute all_commands, string.split(" ")
|
57
56
|
i = i + 1
|
58
57
|
end
|
59
58
|
end
|
60
59
|
|
61
60
|
# read-eval-print step
|
62
|
-
|
61
|
+
# check if argv is in any of all_commands, parse options
|
62
|
+
# according to the specification in all_commands and invoke
|
63
|
+
# a function in this class to actually do the work
|
64
|
+
def self.execute(all_commands, argv)
|
63
65
|
if argv == [] or argv[0] == "--help" or argv[0] == "-h"
|
64
|
-
|
66
|
+
help
|
65
67
|
exit 0
|
66
68
|
else
|
67
69
|
command = argv[0]
|
68
|
-
|
69
|
-
if syntax_and_semantics
|
70
|
-
opts = syntax_and_semantics[0]
|
71
|
-
function = syntax_and_semantics[1]
|
72
|
-
|
73
|
-
begin
|
74
|
-
parser = Slop::Parser.new(opts)
|
70
|
+
command_spec = all_commands[command.to_sym]
|
75
71
|
|
76
|
-
|
77
|
-
|
78
|
-
|
72
|
+
if command_spec
|
73
|
+
command_name = command_spec[:name]
|
74
|
+
option_parser = command_spec[:options]
|
79
75
|
|
80
|
-
|
81
|
-
|
82
|
-
|
76
|
+
begin
|
77
|
+
argv.shift # remove command name from ARGV
|
78
|
+
options = {}
|
79
|
+
parser = option_parser.parse!(into: options)
|
80
|
+
eval "CommandSemantics::#{command_name}(options, argv)"
|
83
81
|
rescue Exception => e
|
84
|
-
puts e
|
82
|
+
puts "#{APPNAME} error: #{e}"
|
83
|
+
puts "Help with \"#{APPNAME} help #{command_name}\""
|
85
84
|
end
|
86
85
|
else
|
87
|
-
puts "#{APPNAME}:
|
86
|
+
puts "#{APPNAME} error: "#{command}" is not a valid command."
|
87
|
+
puts "List commands with \"#{APPNAME} help\"."
|
88
88
|
end
|
89
89
|
end
|
90
90
|
end
|
@@ -92,23 +92,18 @@ module Resme
|
|
92
92
|
#
|
93
93
|
# APP SPECIFIC COMMANDS
|
94
94
|
#
|
95
|
-
def self.check
|
96
|
-
|
97
|
-
|
98
|
-
# schema = YAML.load_file('schema.yaml')
|
95
|
+
def self.check(opts, argv)
|
96
|
+
path = File.join(File.dirname(__FILE__), "/../templates/schema.yml")
|
97
|
+
schema = Kwalify::Yaml.load_file(path)
|
99
98
|
|
100
|
-
|
99
|
+
# create validator
|
101
100
|
validator = Kwalify::Validator.new(schema)
|
102
|
-
|
103
|
-
## load document
|
101
|
+
# load document
|
104
102
|
document = Kwalify::Yaml.load_file(argv[0])
|
105
|
-
|
106
|
-
#document = YAML.load_file('document.yaml')
|
107
|
-
|
108
|
-
## validate
|
103
|
+
# validate
|
109
104
|
errors = validator.validate(document)
|
110
105
|
|
111
|
-
|
106
|
+
# show errors
|
112
107
|
if errors && !errors.empty?
|
113
108
|
for e in errors
|
114
109
|
puts "[#{e.path}] #{e.message}"
|
@@ -118,14 +113,16 @@ module Resme
|
|
118
113
|
end
|
119
114
|
end
|
120
115
|
|
121
|
-
def self.init
|
116
|
+
def self.init(opts, argv)
|
122
117
|
output = opts[:output] || "resume.yml"
|
123
118
|
force = opts[:force]
|
124
|
-
|
119
|
+
path = File.dirname(__FILE__), "/../templates/resume.yml"
|
120
|
+
template = File.join(path)
|
125
121
|
|
126
|
-
# avoid
|
127
|
-
if File.exist?(output)
|
128
|
-
puts "
|
122
|
+
# avoid catastrophe
|
123
|
+
if File.exist?(output) && !force
|
124
|
+
puts "#{APPNAME} error: file #{output} already exists."
|
125
|
+
puts "Use --force if you want to overwrite it."
|
129
126
|
else
|
130
127
|
content = File.read(template)
|
131
128
|
backup_and_write output, content
|
@@ -133,60 +130,65 @@ module Resme
|
|
133
130
|
end
|
134
131
|
end
|
135
132
|
|
136
|
-
def self.
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
puts "
|
133
|
+
def self.list(opts, argv)
|
134
|
+
data = {}
|
135
|
+
argv.each do |file|
|
136
|
+
data = data.merge(YAML.load_file(file, permitted_classes: [Date]))
|
137
|
+
end
|
138
|
+
puts "Sections included in #{argv.join(", ")}:"
|
139
|
+
data.keys.each do |key|
|
140
|
+
puts "- #{key}: #{(data[key] || []).size} entries"
|
141
|
+
end
|
142
142
|
end
|
143
143
|
|
144
|
-
def self.
|
145
|
-
|
146
|
-
|
144
|
+
def self.generate(opts, argv)
|
145
|
+
format = opts[:to] == "europass" ? "xml" : opts[:to]
|
146
|
+
output = opts[:output] || "resume-#{Date.today}.#{format}"
|
147
147
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
output = opts[:output] || "resume-#{Date.today}.json"
|
154
|
-
template = File.join(File.dirname(__FILE__), "/../templates/resume.json.erb")
|
148
|
+
if opts[:erb]
|
149
|
+
template = opts[:erb]
|
150
|
+
else
|
151
|
+
template = File.join(File.dirname(__FILE__), "/../templates/resume.#{format}.erb")
|
152
|
+
end
|
155
153
|
|
156
|
-
|
157
|
-
puts "Resume generated in #{output}"
|
158
|
-
end
|
154
|
+
skipped_sections = opts[:skip] || []
|
159
155
|
|
160
|
-
|
161
|
-
|
162
|
-
|
156
|
+
if !File.exists?(template)
|
157
|
+
puts "#{APPNAME} error: format #{format} is not understood."
|
158
|
+
end
|
163
159
|
|
164
|
-
render argv, template, output
|
160
|
+
render argv, template, output, skipped_sections
|
165
161
|
puts "Resume generated in #{output}"
|
166
|
-
|
162
|
+
|
163
|
+
if format == "xml" then
|
164
|
+
puts "Europass XML generated. Render via, e.g., http://interop.europass.cedefop.europa.eu/web-services/remote-upload/"
|
165
|
+
end
|
167
166
|
end
|
168
167
|
|
169
168
|
private
|
170
169
|
|
171
|
-
def self.render
|
172
|
-
data =
|
170
|
+
def self.render(yml_files, template_name, output_name, skipped_sections)
|
171
|
+
data = {}
|
173
172
|
yml_files.each do |file|
|
174
|
-
data = data.merge(YAML.load_file(file))
|
173
|
+
data = data.merge(YAML.load_file(file, permitted_classes: [Date]))
|
174
|
+
end
|
175
|
+
skipped_sections.each do |section|
|
176
|
+
data.reject! { |k| k == section }
|
175
177
|
end
|
176
|
-
template = File.read(
|
177
|
-
output = ERB.new(template,
|
178
|
+
template = File.read(template_name)
|
179
|
+
output = ERB.new(template, trim_mode: "-").result(binding)
|
178
180
|
# it is difficult to write readable ERBs with no empty lines...
|
179
181
|
# we use gsub to replace multiple empty lines with \n\n in the final output
|
180
182
|
output.gsub!(/([\t ]*\n){3,}/, "\n\n")
|
181
|
-
backup_and_write
|
183
|
+
backup_and_write output_name, output
|
182
184
|
end
|
183
185
|
|
184
|
-
def self.backup
|
186
|
+
def self.backup(filename)
|
185
187
|
FileUtils::cp filename, filename + "~"
|
186
188
|
puts "Backup copy #{filename} created in #{filename}~."
|
187
189
|
end
|
188
190
|
|
189
|
-
def self.backup_and_write
|
191
|
+
def self.backup_and_write(filename, content)
|
190
192
|
backup(filename) if File.exist?(filename)
|
191
193
|
File.open(filename, "w") { |f| f.puts content }
|
192
194
|
end
|