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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6651b4bfa0fa13684adffb20867adec59cbc93d7d611451f68b042e0f41147d
4
- data.tar.gz: f53176624b4f6dd462bcedeee0ac9d1233b3dfa374ef377cd7237c60878733c6
3
+ metadata.gz: 88b2d8e225f97233ed991e95c8be2798b982d803941dacb7fd71b623a32e7822
4
+ data.tar.gz: 7f44a0bf98b3e49b33fce8924ae74545aa6591bfec53f1952aa9067167a8079c
5
5
  SHA512:
6
- metadata.gz: 8ef0373b98367dd22913213ef546899b8f20161d5e3cd8e4db4982e8d5602d9b3006fade7f899ce6010a82dda06a7f306c7f702150d8a099a7f39dff19015a24
7
- data.tar.gz: 2bf517a51726b9a89d6a7f71b5ba50b612c69283ed3f56900477d81b1f036183a4203448f7503087e4c817ed69ac2a57a9b56ae0e522ba7f63d70cf678e98afd
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
- sectins hich are not relevant for your resume.
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
- Once you have started filling you resume, you can then generate a
42
- resume using the existing templates or by writing your own template
43
- (see below).
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 Json format (https://jsonresume.org/):
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
- similar to what Jekyll and Middleman do with data files. (See,
84
- e.g., [data files](https://middlemanapp.com/advanced/data-files/.)
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
- length lower or equal to `n` (useful if you are outputing a txt format,
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 some templates in `lib/resme/templates`. These might be
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 information outputted in the
158
- various formats. For instance, gender and birthdate are used in the
159
- Europass format, but not in the Markdown format. This is in part due
160
- to the different standards and in part due to personal choices (here
161
- "personal choices" could be also read "bug").
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
- Unkown number of unkown bugs.
180
+ Unknown number of unknown bugs.
167
181
 
168
182
  ## Release History
169
183
 
170
- * **0.1** is th first release
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 YML template for your resume in the current directory.
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 YML input files.
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 YML input files.
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 YML input files.
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
- # template
3
+ # templates
4
4
  #
5
5
 
6
6
  # remove spaces at the beginning of string
7
- # remove extra spaces and special chars with a single space
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 reflow string, chars
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])] + reflow(string[index+1..-1], chars)
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])] + reflow(string[index+1..-1], chars)
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 "WARNING!! The value of key '#{key}' is nil."
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 "\t#{k}: #{self[k]}"
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 "ERROR!! Key '#{key}' not found in the following entry."
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]}"