resme 0.4.0 → 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.
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
-