resme 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8abd7d17f0d2b9a777cba680826c21d6ee882f3f3df0c74ba086e1d617d4b6b
4
- data.tar.gz: b34b33c2f2d0f3beae3bef713efba2e39b05537d8a208380e6392271e0cbcb09
3
+ metadata.gz: 27d07b29ff7369747e8f43ad9cbdc29efe1023cca20de554f94c02fdc33c57f8
4
+ data.tar.gz: fc048d5417183cba079e387caa3c08d2540fb937a384aae05759622b91b82846
5
5
  SHA512:
6
- metadata.gz: fe74aa311a4dd110c26a0760cf6d2f8cb1ba1ee8e587f2a64d00ff6aa420a2a7f6c37b1eec392ddf1b28abbe49ffb94d6b26d5f43f2797ba9fd7cc9874ebf5c1
7
- data.tar.gz: 3f2ae60c5c8a4e7f8135aa7f43aafa081f913dc8dd6c1b4c6065606447a41b6afe414ae4f32e0541fc06284227d94628f31f59e80af02aee2147c054032137c8
6
+ metadata.gz: 77bab2cbc35e21bb7ce669100d6b0f66d4b9f60e496814b35aec3a1fb3346730bbfe0700d884cde697500a479007cbac61dc834dcd358b3b1ab4375e59ee60db
7
+ data.tar.gz: fa2c84c4358a0c86469f83f11cb8c361a8a90602ed58eadf8cf53701757d4525d1f27f27599fdf361af46a2fcce0243c2e014395375c84a82489834be5b56118
data/CHANGELOG.org ADDED
@@ -0,0 +1,19 @@
1
+ #+TITLE: CHANGELOG
2
+ #+AUTHOR: Adolfo Villafiorita
3
+
4
+ * Version 1.5.0
5
+ - [user] New command =view= allows to view the template used
6
+ for generating a resume in a specific format
7
+ - [user] Check command is now based on ClassyHash
8
+ - [code] Various changes to the code
9
+
10
+ * Version 1.4.0
11
+ - [user] New option =--skip= allows to skip some top-level sections.
12
+ You mileage might vary, as some formats might require the
13
+ information you are trying to skip
14
+ - [user] New command =generate= is now used to generate the Resume
15
+ in different formats.
16
+ - [user] New option =--erb= allows to specify a custom template for
17
+ generating the resume. Use it in conjunction with =view= (released
18
+ in version 1.5.0) to jump-start your layout.
19
+
data/README.org CHANGED
@@ -64,18 +64,33 @@ Supported formats include:
64
64
  - =json=: JSON format (https://jsonresume.org/)
65
65
 
66
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.
67
+ your own (see below).
69
68
 
70
- Remarks:
69
+ Notice that you can specify more than one YML file in input. This allows you to
70
+ store data about your resume in different files, if you like to do so
71
+ (e.g., work experiences could be in one file and talks in another).
72
+ The YML files are merged before processing them.
71
73
 
72
- - You can specify more than one YML file in input. This allows you to
73
- store data about your resume in different files, if you like to do so
74
- (e.g., work experiences could be in one file and talks in another).
75
- The YML files are merged before processing them.
76
- - The output filename is optional. If you do not specify one, the resume
77
- is generated to =resume-YYYY.MM.DD.format=, where =YYYY-MM-DD= is
78
- today's date and =format= is the chosen output format
74
+ Full syntax:
75
+
76
+ #+begin_src shell :results raw output :wrap example
77
+ resme help
78
+ #+end_src
79
+
80
+ #+RESULTS:
81
+ #+begin_example
82
+ resme command [options] [args]
83
+ Available commands:
84
+ view --template FORMAT # Print template used for format
85
+ console # enter the console
86
+ man # print resme manual page
87
+ help [command] # print command usage
88
+ init [options] # generate an empty resume.yml file
89
+ check resume.yml # Check syntax of resume.yml
90
+ list resume.yml # List main sections in resume.yml
91
+ version # print version information
92
+ generate [options] resume.yml ... # output resume
93
+ #+end_example
79
94
 
80
95
  ** Checking validity
81
96
  :PROPERTIES:
@@ -110,24 +125,32 @@ The third and the forth format allows you to enter "partial" dates
110
125
  :END:
111
126
 
112
127
  The resumes are generated from the YML matter using ERB templates. The
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).
128
+ provided output formats should support different back ends. Org Mode
129
+ and Markdown, for instance, easily allow for generation of PDFs, HTML,
130
+ and ODT, to mention a few.
131
+
132
+ However, if you want, you can define your own templates.
116
133
 
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:
134
+ Use the command =view= to print one of the templates used by =resme=
135
+ and build from that.
136
+
137
+ #+begin_example
138
+ resme view --template md
139
+ #+end_example
140
+
141
+ Notice that all the data in the resume is made available in the =data=
142
+ variable. Thus, for instance, the following code snippets generates a
143
+ list of all the work experiences:
121
144
 
122
145
  #+BEGIN_EXAMPLE
123
- <% data.work each do |exp| %>
124
- - <%= exp.who %>
125
- From: <%= exp.from %> till: <%= exp.till %>
146
+ <% data["work"] each do |exp| %>
147
+ - <%= exp["who"] %>
148
+ From: <%= exp["from"] %> till: <%= exp["till"] %>
126
149
  <% end %>
127
150
  #+END_EXAMPLE
128
151
 
129
- To specify your own ERB template use the option =-e= (=--erb=). Thus,
130
- for instance:
152
+ To specify your own ERB template to build your resume use the option
153
+ =-e= (=--erb=). Thus, for instance:
131
154
 
132
155
  #+BEGIN_EXAMPLE
133
156
  $ resme render -e template.md.erb [-o output_filename] file.yaml ...
@@ -200,6 +223,10 @@ https://github.com/avillafiorita/resme.
200
223
  The gem is available as open source under the terms of the
201
224
  [[http://opensource.org/licenses/MIT][MIT License]].
202
225
 
226
+ ** Change Log
227
+
228
+ In =doc/changelog.org=
229
+
203
230
  ** Roadmap
204
231
  :PROPERTIES:
205
232
  :CUSTOM_ID: roadmap
@@ -5,7 +5,6 @@ require "date"
5
5
  require "yaml"
6
6
  require "erb"
7
7
  require "json"
8
- require "kwalify"
9
8
 
10
9
  module Resme
11
10
  module CommandSemantics
@@ -93,24 +92,24 @@ module Resme
93
92
  # APP SPECIFIC COMMANDS
94
93
  #
95
94
  def self.check(opts, argv)
96
- path = File.join(File.dirname(__FILE__), "/../templates/schema.yml")
97
- schema = Kwalify::Yaml.load_file(path)
98
-
99
- # create validator
100
- validator = Kwalify::Validator.new(schema)
101
- # load document
102
- document = Kwalify::Yaml.load_file(argv[0])
103
- # validate
104
- errors = validator.validate(document)
105
-
106
- # show errors
107
- if errors && !errors.empty?
108
- for e in errors
109
- puts "[#{e.path}] #{e.message}"
95
+ begin
96
+ document = YAML.load_file(argv[0], permitted_classes: [Date])
97
+ rescue Psych::SyntaxError => ex
98
+ puts "The file #{argv[0]} has an invalid structure."
99
+ puts ex.message
100
+ exit 1
101
+ end
102
+
103
+ begin
104
+ errors = ResumeStructureValidator.validate(document)
105
+ rescue Exception => ex
106
+ puts "The files #{argv[0]} does not validate"
107
+ ex.entries.each do |error|
108
+ puts "#{error[:full_path]}: #{error[:message]}"
110
109
  end
111
- else
112
- puts "The file #{argv[0]} validates."
110
+ exit 1
113
111
  end
112
+ puts "The file #{argv[0]} has a valid structure."
114
113
  end
115
114
 
116
115
  def self.init(opts, argv)
@@ -141,6 +140,12 @@ module Resme
141
140
  end
142
141
  end
143
142
 
143
+ def self.view(opts, argv)
144
+ format = opts[:template] == "europass" ? "xml" : opts[:template]
145
+ template = File.join(File.dirname(__FILE__), "/../templates/resume.#{format}.erb")
146
+ puts File.read template
147
+ end
148
+
144
149
  def self.generate(opts, argv)
145
150
  format = opts[:to] == "europass" ? "xml" : opts[:to]
146
151
  output = opts[:output] || "resume-#{Date.today}.#{format}"
@@ -217,6 +217,35 @@ module Resme
217
217
  }
218
218
  end
219
219
 
220
+ def self.view_opts
221
+ opts = OptionParser.new do |o|
222
+ o.banner = "view --template FORMAT # Print template used for format"
223
+ o.on("-t", "--template FORMAT", String, "View template for FORMAT")
224
+ end
225
+
226
+ help = <<-EOS
227
+ NAME
228
+ #{opts.banner}
229
+
230
+ SYNOPSYS
231
+ #{opts.to_s}
232
+
233
+ DESCRIPTION
234
+ Print template used for FORMAT
235
+
236
+ EXAMPLES
237
+ resme view --template md
238
+ EOS
239
+
240
+ {
241
+ view: {
242
+ name: :view,
243
+ options: opts,
244
+ help: help.gsub(" ", "")
245
+ }
246
+ }
247
+ end
248
+
220
249
  def self.generate_opts
221
250
  opts = OptionParser.new do |o|
222
251
  o.banner = "generate [options] resume.yml ... # output resume"
@@ -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,49 +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 (hash["key"] -> hash.key) and
137
- # report errors if key is missing
138
- class Hash
139
- def method_missing(m)
140
- key = m.to_s
141
-
142
- # we put a bit of info about the top level structure of a resume to avoid
143
- # extra-long error messages I don't want to print detailed information
144
- # about top-level entries missing in the resume
145
- top_level_entries = %w[
146
- contacts addresses web_presence summary work teaching projects other
147
- committees volunteer visits education publications talks awards
148
- achievements software skills languages driving interests references
149
- ]
150
-
151
- # error: nil value
152
- if self.has_key? key and self[key] == nil
153
- $stderr.puts "[W] The value of key '#{key}' is nil in the following entry:"
154
-
155
- unless top_level_entries.include?(key)
156
- # $stderr.puts self.to_s
157
- self.keys.each do |k|
158
- $stderr.puts " #{k}: #{self[k]}"
159
- end
160
- $stderr.puts ""
161
- end
162
- end
163
-
164
- return self[key] if self.has_key? key
165
-
166
- # we get here if the key is not found we report an error, return
167
- # "" and continue the actual mileage might vary
168
-
169
- $stderr.puts "[E] Key '#{key}' not found in the following entry:"
170
- unless top_level_entries.include?(key)
171
- self.keys.each do |k|
172
- $stderr.puts " #{k}: #{self[k]}"
173
- end
174
- $stderr.puts ""
175
- end
176
-
177
- return ""
178
- end
179
- end
180
-