resme 0.3.0 → 0.4.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: 88b2d8e225f97233ed991e95c8be2798b982d803941dacb7fd71b623a32e7822
4
- data.tar.gz: 7f44a0bf98b3e49b33fce8924ae74545aa6591bfec53f1952aa9067167a8079c
3
+ metadata.gz: d8abd7d17f0d2b9a777cba680826c21d6ee882f3f3df0c74ba086e1d617d4b6b
4
+ data.tar.gz: b34b33c2f2d0f3beae3bef713efba2e39b05537d8a208380e6392271e0cbcb09
5
5
  SHA512:
6
- metadata.gz: e0bdffd6b716cc6056c158a1672144343a66674c5371136d70c302d6e338279e0e582f0fbd126e577967d6f668557537f6732102faad014ec3478d87c4e2cd9b
7
- data.tar.gz: 8feff2a7f410655dffc1199c6b47d9358acbaf5f0450715608b95dc393ec0ab2a25fc53b87a3accac0e047cca05c7ac2e02c0e2f710051b4af7bcde44e46180c
6
+ metadata.gz: fe74aa311a4dd110c26a0760cf6d2f8cb1ba1ee8e587f2a64d00ff6aa420a2a7f6c37b1eec392ddf1b28abbe49ffb94d6b26d5f43f2797ba9fd7cc9874ebf5c1
7
+ data.tar.gz: 3f2ae60c5c8a4e7f8135aa7f43aafa081f913dc8dd6c1b4c6065606447a41b6afe414ae4f32e0541fc06284227d94628f31f59e80af02aee2147c054032137c8
data/.gitignore CHANGED
@@ -1,11 +1,8 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
- /Gemfile.lock
4
3
  /_yardoc/
5
4
  /coverage/
6
5
  /doc/
7
6
  /pkg/
8
7
  /spec/reports/
9
8
  /tmp/
10
- *~
11
- *.bak
data/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
-
5
3
  # Specify your gem's dependencies in resme.gemspec
6
4
  gemspec
5
+
6
+ gem "rake", "~> 12.0"
data/README.org ADDED
@@ -0,0 +1,245 @@
1
+ #+TITLE: RESME - A Resume Generator
2
+ #+AUTHOR: Adolfo Villafiorita
3
+ #+DATE: <2020-07-14 Tue>
4
+ #+STARTUP: showall
5
+
6
+ Keep your resume in YAML and output it in various formats, including
7
+ Org Mode, Markdown, JSON, and the Europass XML format.
8
+
9
+ The rendering engine is based on ERB. This simplifies the creation of
10
+ new output formats (and extending/modifying the YML structure to one's
11
+ needs).
12
+
13
+ ** Installation
14
+ :PROPERTIES:
15
+ :CUSTOM_ID: installation
16
+ :END:
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ #+BEGIN_SRC ruby
21
+ gem 'resme'
22
+ #+END_SRC
23
+
24
+ And then execute:
25
+
26
+ #+BEGIN_EXAMPLE
27
+ $ bundle
28
+ #+END_EXAMPLE
29
+
30
+ Or install it yourself as:
31
+
32
+ #+BEGIN_EXAMPLE
33
+ $ gem install resme
34
+ #+END_EXAMPLE
35
+
36
+ ** Usage
37
+ :PROPERTIES:
38
+ :CUSTOM_ID: usage
39
+ :END:
40
+
41
+ Start with:
42
+
43
+ #+BEGIN_EXAMPLE
44
+ resme init
45
+ #+END_EXAMPLE
46
+
47
+ which generates a YML template for your resume in the current directory.
48
+ Comments in the YML file should help you fill the various entries.
49
+ Notice that most entries are optional and you can remove sections which
50
+ are not relevant for your resume.
51
+
52
+ You can now generate a resume using one of the existing formats:
53
+
54
+ #+begin_example
55
+ resme --to org resume.yml
56
+ #+end_example
57
+
58
+ Supported formats include:
59
+
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/)
65
+
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.
69
+
70
+ Remarks:
71
+
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
79
+
80
+ ** Checking validity
81
+ :PROPERTIES:
82
+ :CUSTOM_ID: checking-validity
83
+ :END:
84
+
85
+ Use the =check= command to verify whether your YAML file conforms with
86
+ the intended syntax.
87
+
88
+ #+BEGIN_SRC ruby
89
+ resme check resume.yaml
90
+ #+END_SRC
91
+
92
+ ** Dates in the resume
93
+ :PROPERTIES:
94
+ :CUSTOM_ID: dates-in-the-resume
95
+ :END:
96
+
97
+ Enter dates in the resume in one of the following formats:
98
+
99
+ - Any format accepted by Ruby for a full date (year, month, day)
100
+ - YYYY-MM-DD
101
+ - YYYY-MM
102
+ - YYYY
103
+
104
+ The third and the forth format allows you to enter "partial" dates
105
+ (e.g., when the month or the day have been forgotten or are irrelevant).
106
+
107
+ ** Creating your own templates
108
+ :PROPERTIES:
109
+ :CUSTOM_ID: creating-your-own-templates
110
+ :END:
111
+
112
+ 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).
116
+
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:
121
+
122
+ #+BEGIN_EXAMPLE
123
+ <% data.work each do |exp| %>
124
+ - <%= exp.who %>
125
+ From: <%= exp.from %> till: <%= exp.till %>
126
+ <% end %>
127
+ #+END_EXAMPLE
128
+
129
+ To specify your own ERB template use the option =-e= (=--erb=). Thus,
130
+ for instance:
131
+
132
+ #+BEGIN_EXAMPLE
133
+ $ resme render -e template.md.erb [-o output_filename] file.yaml ...
134
+ #+END_EXAMPLE
135
+
136
+ uses =template.md.erb= to generate the resume.
137
+
138
+ Some functions can be used in the templates to better control the
139
+ output.
140
+
141
+ String manipulation functions:
142
+
143
+ - =clean string= removes any space at the beginning of =string=
144
+ - =reflow string, n= makes =string= into an array of strings of length
145
+ lower or equal to =n= (useful if you are outputting a textual format,
146
+ for instance.
147
+
148
+ Dates manipulation functions:
149
+
150
+ - =period= generates a string recapping a period. The function abstracts
151
+ different syntax you can use for entries (i.e., =date= or =from= and
152
+ =till=) and different values for the entries (e.g., a missing value
153
+ for =till=)
154
+ - =year string=, =month string=, =day string= return, respectively the
155
+ year, month and day from strings in the format =YYYY-MM-DD=s
156
+ - =has_month input= returns true if =input= has a month, that is, it is
157
+ a date or it is in the form =YYYY-MM=
158
+ - =has_day input= returns true if =input= has a day, that is, it is a
159
+ date or it is in the form =YYYY-MM-DD=
160
+
161
+ You can find the templates in =lib/resme/templates=. These might be good
162
+ starting points if you want to develop your own.
163
+
164
+ ** Contributing your templates
165
+ :PROPERTIES:
166
+ :CUSTOM_ID: contributing-your-templates
167
+ :END:
168
+
169
+ If you develop an output template and want to make it available, please
170
+ let me know, so that I can include it in future releases of this gem.
171
+
172
+ ** Development
173
+ :PROPERTIES:
174
+ :CUSTOM_ID: development
175
+ :END:
176
+
177
+ After checking out the repo, run =bin/setup= to install dependencies.
178
+ You can also run =bin/console= for an interactive prompt that will allow
179
+ you to experiment.
180
+
181
+ To install this gem onto your local machine, run
182
+ =bundle exec rake install=. To release a new version, update the version
183
+ number in =version.rb=, and then run =bundle exec rake release=, which
184
+ will create a git tag for the version, push git commits and tags, and
185
+ push the =.gem= file to [[https://rubygems.org][rubygems.org]].
186
+
187
+ ** Contributing
188
+ :PROPERTIES:
189
+ :CUSTOM_ID: contributing
190
+ :END:
191
+
192
+ Bug reports and pull requests are welcome on GitHub at
193
+ https://github.com/avillafiorita/resme.
194
+
195
+ ** License
196
+ :PROPERTIES:
197
+ :CUSTOM_ID: license
198
+ :END:
199
+
200
+ The gem is available as open source under the terms of the
201
+ [[http://opensource.org/licenses/MIT][MIT License]].
202
+
203
+ ** Roadmap
204
+ :PROPERTIES:
205
+ :CUSTOM_ID: roadmap
206
+ :END:
207
+
208
+ In =doc/todo.org= ... guess what is my preferred editor!
209
+
210
+ ** Bugs
211
+ :PROPERTIES:
212
+ :CUSTOM_ID: bugs
213
+ :END:
214
+
215
+ There are still slight differences in the syntax of entries and in the
216
+ way in which the information is formatted in various output formats. For
217
+ instance, gender and birthdate are used in the Europass format, but not
218
+ in the Markdown format. This is in part due to the different standards
219
+ and in part due to personal preferences.
220
+
221
+ *Entries are not sorted by date before outputting them. Make sure you
222
+ put them in the order you want them to appear in your resume.*
223
+
224
+ Unknown number of unknown bugs.
225
+
226
+ ** Release History
227
+ :PROPERTIES:
228
+ :CUSTOM_ID: release-history
229
+ :END:
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.
235
+ - *0.3.2* and *0.3.1* fix errors with the Europass format: lists of
236
+ projects, interests, ... are now properly formatted.
237
+ - *0.3* introduces output to org-mode, introduces references for the CV,
238
+ improves output to JSON, adds a =check= command, removes useless blank
239
+ lines in the output, supports =-%>= in the ERB templates, fixes
240
+ various typos in the documentation, introduces various new formatting
241
+ functions, to simplify template generation
242
+ - *0.2* improves output of volunteering activities and other information
243
+ in the Europass and *significantly improves error and warning
244
+ reporting*
245
+ - *0.1* is the first release
data/exe/resme CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'resme'
3
+ require "resme"
4
4
 
5
5
  # read-eval-print-step passing all commands and the argument
6
- Resme::CommandSemantics.reps Resme::CommandSyntax.commands, ARGV
6
+ Resme::CommandSemantics.execute Resme::CommandSyntax.commands, ARGV
@@ -1,90 +1,90 @@
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'
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 = 'resme'
12
+ APPNAME = "resme"
13
13
  VERSION = Resme::VERSION
14
14
 
15
15
  #
16
16
  # Main App Starts Here!
17
17
  #
18
- def self.version opts = nil, argv = []
18
+ def self.version(opts = nil, argv = [])
19
19
  puts "#{APPNAME} version #{VERSION}"
20
20
  end
21
21
 
22
- def self.man opts = nil, argv = []
23
- path = File.join(File.dirname(__FILE__), "/../../../README.md")
22
+ def self.man(opts = nil, argv = [])
23
+ path = File.join(File.dirname(__FILE__), "/../../../README.org")
24
24
  file = File.open(path, "r")
25
- contents = file.read
26
- puts contents
25
+ puts file.read
27
26
  end
28
27
 
29
- def self.help opts = nil, argv = []
28
+ def self.help(opts = nil, argv = [])
30
29
  all_commands = CommandSyntax.commands
31
30
 
32
31
  if argv != []
33
- argv.map { |x| puts all_commands[x.to_sym][2] }
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
- puts "Available commands:"
38
- puts ""
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 opts, argv = []
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
- string.gsub!(/^#{APPNAME} /, "") # as a courtesy, remove any leading appname string
53
- if string == "exit" or string == "quit" or string == "." then
54
- exit 0
55
- end
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
- def self.reps all_commands, argv
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
- CommandSemantics.help
66
+ help
65
67
  exit 0
66
68
  else
67
69
  command = argv[0]
68
- syntax_and_semantics = all_commands[command.to_sym]
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
- result = parser.parse(argv[1..-1])
77
- options = result.to_hash
78
- arguments = result.arguments
72
+ if command_spec
73
+ command_name = command_spec[:name]
74
+ option_parser = command_spec[:options]
79
75
 
80
- eval "CommandSemantics::#{function}(options, arguments)"
81
- rescue Slop::Error => e
82
- puts "#{APPNAME}: #{e}"
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}: '#{command}' is not a valid command. See '#{APPNAME} help'"
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 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')
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
- ## create validator
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
- ## or
106
- #document = YAML.load_file('document.yaml')
107
-
108
- ## validate
103
+ # validate
109
104
  errors = validator.validate(document)
110
105
 
111
- ## show errors
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 opts, argv
116
+ def self.init(opts, argv)
122
117
  output = opts[:output] || "resume.yml"
123
118
  force = opts[:force]
124
- template = File.join(File.dirname(__FILE__), "/../templates/resume.yml")
119
+ path = File.dirname(__FILE__), "/../templates/resume.yml"
120
+ template = File.join(path)
125
121
 
126
- # avoid catastrophy
127
- if File.exist?(output) and not force
128
- puts "Error: file #{output} already exists. Use --force if you want to overwrite it"
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.md opts, argv
137
- output = opts[:output] || "resume-#{Date.today}.md"
138
- template = File.join(File.dirname(__FILE__), "/../templates/resume.md.erb")
139
-
140
- render argv, template, output
141
- puts "Resume generated in #{output}"
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.org opts, argv
145
- output = opts[:output] || "resume-#{Date.today}.org"
146
- template = File.join(File.dirname(__FILE__), "/../templates/resume.org.erb")
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
- render argv, template, output
149
- puts "Resume generated in #{output}"
150
- end
151
-
152
- def self.json opts, argv
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
- render argv, template, output
157
- puts "Resume generated in #{output}"
158
- end
154
+ skipped_sections = opts[:skip] || []
159
155
 
160
- def self.europass opts, argv
161
- output = opts[:output] || "resume-#{Date.today}.xml"
162
- template = File.join(File.dirname(__FILE__), "/../templates/europass/eu.xml.erb")
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
- puts "Render via, e.g., http://interop.europass.cedefop.europa.eu/web-services/remote-upload/"
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 yml_files, template_filename, output_filename
172
- data = Hash.new
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(template_filename)
177
- output = ERB.new(template, nil, '-').result(binding)
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 output_filename, output
183
+ backup_and_write output_name, output
182
184
  end
183
185
 
184
- def self.backup filename
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 filename, content
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