davetron5000-daves-resume 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/dr +13 -0
- data/bin/dr-format +69 -0
- data/bin/dr-scaffold +51 -0
- data/ext/make_commandline_gemproof.rb +19 -0
- data/ext/position_filter.rb +40 -0
- data/ext/string_tags.rb +4 -0
- data/lib/README.rdoc +112 -0
- data/lib/formatter.rb +53 -0
- data/lib/resume.rb +399 -0
- data/lib/serializer.rb +121 -0
- data/templates/HTML.erb +70 -0
- data/templates/Markdown.erb +49 -0
- data/templates/RTF.erb +182 -0
- metadata +77 -0
data/bin/dr
ADDED
data/bin/dr-format
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#
|
3
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/../ext')
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'commandline'
|
8
|
+
require 'resume'
|
9
|
+
require 'serializer'
|
10
|
+
require 'formatter'
|
11
|
+
require 'fileutils'
|
12
|
+
require 'make_commandline_gemproof'
|
13
|
+
|
14
|
+
include Resume
|
15
|
+
|
16
|
+
class DavesResume < CommandLine::Application
|
17
|
+
def initialize
|
18
|
+
author "David Copeland"
|
19
|
+
synopsis "Usage: #{$0} --format file_format [file_format]* --resume resume_yaml_dir [--core core_name] [--name base_name]"
|
20
|
+
short_description "Resume builder and formatter"
|
21
|
+
long_description "This reads your resume in YAML and outputs it in a given file format (e.g. RTF). This also allows you to specify multiple resume cores and to choose between them. For example, if you are searching for a senior developer and a technical lead position, you might want to change your headline and summary, depending on the position. Further, you may want to customize which achievements of your post positions you focus on."
|
22
|
+
|
23
|
+
options :help
|
24
|
+
option :names => %w(--format -f),
|
25
|
+
:opt_description => "Formats to generate for your resume",
|
26
|
+
:arg_description => "One or more format names matching existing templates",
|
27
|
+
:arity => [1,-1],
|
28
|
+
:opt_found => get_args,
|
29
|
+
:opt_not_found => lambda {
|
30
|
+
puts synopsis
|
31
|
+
exit
|
32
|
+
}
|
33
|
+
option :names => %w(--resume -r),
|
34
|
+
:opt_description => "Sets the path to the resume YAML files",
|
35
|
+
:arity => [1,1],
|
36
|
+
:opt_found => get_args,
|
37
|
+
:opt_not_found => lambda {
|
38
|
+
puts synopsis
|
39
|
+
exit
|
40
|
+
}
|
41
|
+
option :names => %w(--filter),
|
42
|
+
:opt_description => "Path to a ruby file that implements the 'Key Achievements' filter",
|
43
|
+
:arity => [1,1],
|
44
|
+
:opt_found => get_args,
|
45
|
+
:opt_not_found => false
|
46
|
+
|
47
|
+
option :names => %w(--core -c),
|
48
|
+
:opt_description => "Sets the core for this resume generation",
|
49
|
+
:arg_description => "Name of the core to use (should be in resume_dir named resume_CORENAME.yaml)",
|
50
|
+
:arity => [1,1],
|
51
|
+
:opt_found => get_args,
|
52
|
+
:opt_not_found => false
|
53
|
+
|
54
|
+
option :names => %w(--name -n),
|
55
|
+
:opt_description => "Controls the name of the generated file",
|
56
|
+
:arg_description => "Base name of the file(s) to generate (extension is based on formats selected)",
|
57
|
+
:arity => [1,1],
|
58
|
+
:opt_found => get_args,
|
59
|
+
:opt_not_found => 'resume'
|
60
|
+
end
|
61
|
+
|
62
|
+
def main
|
63
|
+
serializer = Serializer.new
|
64
|
+
serializer.core_to_use = opt.core if opt.core
|
65
|
+
serializer.filter_file = opt.filter if opt.filter
|
66
|
+
formatter = Formatter.new(serializer.load(opt.resume),opt.name)
|
67
|
+
formatter.send(opt.format.to_sym)
|
68
|
+
end
|
69
|
+
end
|
data/bin/dr-scaffold
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/../ext')
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'commandline'
|
8
|
+
require 'resume'
|
9
|
+
require 'serializer'
|
10
|
+
require 'formatter'
|
11
|
+
require 'fileutils'
|
12
|
+
require 'make_commandline_gemproof'
|
13
|
+
|
14
|
+
include Resume
|
15
|
+
|
16
|
+
class DavesResume < CommandLine::Application
|
17
|
+
def initialize
|
18
|
+
author "David Copeland"
|
19
|
+
synopsis "Usage: #{$0} [--force] --resume resume_dir"
|
20
|
+
short_description "Generates scaffold of a resume for Dave's Resume"
|
21
|
+
long_description "This will generate a series of YAML files that you can use as the basis for your resume. You can create as many experience and education files as you like; these will all be picked up and sorted appropriately. See the README for more"
|
22
|
+
|
23
|
+
options :help
|
24
|
+
|
25
|
+
option :flag, :names => %w(--force -f),
|
26
|
+
:opt_description => "Force overwrite of existing scaffold dirs"
|
27
|
+
|
28
|
+
option :names => %w(--resume -r),
|
29
|
+
:opt_description => "Sets the path to put the resume YAML files",
|
30
|
+
:arity => [1,1],
|
31
|
+
:opt_found => get_args,
|
32
|
+
:opt_not_found => lambda {
|
33
|
+
puts synopsis
|
34
|
+
exit
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def main
|
39
|
+
resume = Resume::Resume.scaffold
|
40
|
+
serializer = Serializer.new
|
41
|
+
scaffold_dir = opt.resume
|
42
|
+
if (opt.force || (!File.exist? scaffold_dir))
|
43
|
+
rm_rf(scaffold_dir)
|
44
|
+
mkdir(scaffold_dir)
|
45
|
+
serializer.store(scaffold_dir,resume)
|
46
|
+
puts "Your scaffold resume is in #{scaffold_dir}"
|
47
|
+
else
|
48
|
+
puts "#{scaffold_dir} exists, use --force to overwrite"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'commandline'
|
2
|
+
|
3
|
+
# Makes the inherited method not be so strict checking the app name
|
4
|
+
class CommandLine::Application
|
5
|
+
class << self
|
6
|
+
alias old_inherited inherited
|
7
|
+
|
8
|
+
def inherited(child_class)
|
9
|
+
@@appname = caller[0][/.*:/][0..-2]
|
10
|
+
@@child_class = child_class
|
11
|
+
normalized_appname = @@appname.gsub(/^.*\//,"");
|
12
|
+
normalized_dollar0 = $0.gsub(/^.*\//,"");
|
13
|
+
if normalized_appname == normalized_dollar0
|
14
|
+
__set_auto_run
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class KeyAcheivementsFilter
|
2
|
+
|
3
|
+
attr_accessor :filter_config
|
4
|
+
|
5
|
+
def filter_achievements(achievements)
|
6
|
+
counts = Hash.new
|
7
|
+
developer_not_architect = Array.new
|
8
|
+
filtered = achievements.select() do |a|
|
9
|
+
if !a.tags || a.tags.empty?
|
10
|
+
filter_config.include_untagged_achievements
|
11
|
+
else
|
12
|
+
a.tags.each() { |t| counts[t] = counts[t] ? counts[t] += 1 : 1 }
|
13
|
+
developer_not_architect << a if (!a.tags.include? filter_config.primary_tag) && (a.tags.include? filter_config.alt_tag)
|
14
|
+
if (a.tags.include? :major)
|
15
|
+
true
|
16
|
+
else
|
17
|
+
! ((a.tags & filter_config.achievement_tags).empty?)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
if !counts[filter_config.primary_tag]
|
22
|
+
filtered = filtered | developer_not_architect
|
23
|
+
end
|
24
|
+
if filtered.size < filter_config.min_achievements
|
25
|
+
needed = filter_config.min_achievements - filtered.size()
|
26
|
+
needed.times() { |i| filtered << achievements[i] if achievements[i] && !filtered.include?(achievements[i]) }
|
27
|
+
elsif filtered.size > filter_config.max_achievements
|
28
|
+
filtered = filtered.select() do |a|
|
29
|
+
if !a.tags || a.tags.empty?
|
30
|
+
true
|
31
|
+
else
|
32
|
+
!a.tags.include?(:minor)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
filtered
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
FILTER = KeyAcheivementsFilter.new
|
data/ext/string_tags.rb
ADDED
data/lib/README.rdoc
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
= Dave's Resume
|
2
|
+
|
3
|
+
This is a set of small tools that allow you to manage your resume as a "database" of sorts, and publish it in various formats. You can use it to maintain multiple resumes based on the same employment history and education, which can be handy for targeting certain jobs.
|
4
|
+
|
5
|
+
= Quick Start
|
6
|
+
|
7
|
+
dr-scaffold -r my_resume_dir
|
8
|
+
dr-format -r my_resume_dir -f HTML
|
9
|
+
|
10
|
+
<tt>resume.html</tt> now contains the resume generated by the scaffold. To create you resume, you need to:
|
11
|
+
|
12
|
+
1. Modify <tt>resume.yaml</tt> with your information
|
13
|
+
2. Modify <tt>skills.yaml</tt> with your skillset (see below)
|
14
|
+
3. Duplicate <tt>experience_Initech.yaml</tt> for each job you've held; this contains all the positions you held at that job, plus other information
|
15
|
+
4. Duplicate <tt>education_Degree_Mill_U.yaml</tt> for each degree/education you wish to appear
|
16
|
+
5. Duplicate (or delete) <tt>samples_Name_of_this_work_sample.yaml</tt> with any work samples
|
17
|
+
|
18
|
+
You can generate in one of three formats:
|
19
|
+
<tt>RTF</tt>:: - Rich Text which can be read by Word
|
20
|
+
<tt>HTML</tt>:: - A basic HTML version
|
21
|
+
<tt>Markdown</tt>:: - a Markdown[http://www.daringfireball.com/projects/markdown] version
|
22
|
+
|
23
|
+
|
24
|
+
= YAML format
|
25
|
+
|
26
|
+
Basically, this is the serialized form of the classes in <tt>resume.rb</tt>, as produced by the standard YAML serializer that comes with Ruby.
|
27
|
+
|
28
|
+
So, if you edit this and get the formatting wrong, you get obtuse errors. Be gentle and thank yourself you don't have to do it in Word. Also consider using source control. Now that you've decided to store your resume in a sane, text-based format, you get all the benefits of version control, including diffs and reverting to known versions.
|
29
|
+
|
30
|
+
Basically, the resume is:
|
31
|
+
|
32
|
+
Core:: (in <tt>resume.yaml</tt>) - This has stuff like your name, address, summary, etc. If you wish to have multiple resumes, you will duplicate this file naming it <tt>resume_NAME.yaml</tt> where _NAME_ is the "core name" you can specify on the command-linen to <tt>dr-format</tt>
|
33
|
+
Skill Set:: (in <tt>skills.yaml</tt>) - This is a database of your skills, with an experience level and a number of years experience. These determine the sort order in your resume output. Basically, skills with which you are an <tt>:expert</tt> are shown first, sorted by years of experience. This is followed by skills where you are <tt>:intermediate</tt>. <tt>:novice</tt> skills show up in their own category called "Some Experience With". This allows you to include skills you've used, but be clear where your strengths lie.
|
34
|
+
* The accepted levels currently are:
|
35
|
+
<tt>:novice</tt>:: skills show up in "Some Experience With"
|
36
|
+
<tt>:intermediate</tt>:: skills show up last in their category
|
37
|
+
<tt>:expert</tt>:: skills show up first in their category
|
38
|
+
* The categories are currently hard-coded (sorry) and any skill not in a category won't show up (this is on my todo list). The categories are:
|
39
|
+
<tt>:languages</tt>:: Programming languages, e.g.
|
40
|
+
<tt>:apis</tt>:: APIs, standards, etc.
|
41
|
+
<tt>:tools</tt>:: Specific technology tools
|
42
|
+
<tt>:databases</tt>:: Relational databases
|
43
|
+
<tt>:operating_systems</tt>:: Specific operating systems
|
44
|
+
Experience:: any file that starts with <tt>experience_</tt> and ends in <tt>.yaml</tt> will get picked up. Things will be sorted properly (in reverse chronological order) for you. A Job consists of one or more positions, and the formatter should be smart about handling jobs with only one position.
|
45
|
+
* Acheivements can be tagged and you can use these tags to control your output (see below)
|
46
|
+
Education:: works like Experience files starting with <tt>education_</tt> will get picked up and sorted reverse chronologically
|
47
|
+
Samples:: this is just a name and a link, and only shows up in the Markdown and HTML version (though you are free to modify the RTF version).
|
48
|
+
References:: this isn't used anywhere right now, but any file starting with <tt>reference_</tt> and ending with <tt>.yaml</tt> will get picked up.
|
49
|
+
|
50
|
+
= Tweaking the output
|
51
|
+
|
52
|
+
The output is created via ERB templates. Currently, these templates are part of the gem installation and not terribly tweakable. In the future you should be able to create your own. If you are feeling up for it, you can tweak those in the gem install directly.
|
53
|
+
|
54
|
+
= Maintaining multiple resumes
|
55
|
+
|
56
|
+
The idea of Dave's Resume is that your resume database keeps *all* relevant data about your work experience. The realities of the job market are that you need to focus your resume for a particular job. As such, you might not want to include everything in your past on your resume. For example, suppose you had a job with these three key achievements:
|
57
|
+
|
58
|
+
* Designed and Developed Enterprise Java Application for issue management
|
59
|
+
* Patched Windows machines for HR staff
|
60
|
+
* Conducted regular design and code reviews
|
61
|
+
|
62
|
+
If you were going for a Senior Java Developer position, you might not want to include the achievement regarding patching Windows machines (conversely if you were going for a network administrator position, maybe you would).
|
63
|
+
|
64
|
+
Filter allows you to customize this behavior.
|
65
|
+
|
66
|
+
You have two means of doing this:
|
67
|
+
|
68
|
+
* Create multiple "resume cores" with different headlines and summaries. Here, just create, say <tt>resume_javadev.yaml</tt> and <tt>resume_networkadmin.yaml</tt> instead of the <tt>resume.yaml</tt> the scaffolding created. Modify these as needed and then do <tt>dr-format -r resume_dir -f RTF --core javadev</tt> to generate your Java Developer focused resume
|
69
|
+
* Create filtering code in Ruby to filter your "achievements". Here you create a class that implements <tt>filter_acheivements</tt>. It takes an array of strings (where tags are available via <tt>String=tags</tt>) and should return an array of strings, filtered based on your criteria (presumably the tags in your database). Create an instance of this class in your file and assign it to the <tt>FILTER</tt> constant. Pass the path to this file to the <tt>--filter</tt> argument of <tt>dr-format</tt>.
|
70
|
+
|
71
|
+
There is a default filter available that might do what you need
|
72
|
+
|
73
|
+
== Included Acheivement Filter
|
74
|
+
|
75
|
+
The included filter works as follows:
|
76
|
+
|
77
|
+
1. Create a core for the job (e.g. <tt>techlead_resume.yaml</tt>)
|
78
|
+
1. Create a config file in your resume dir named <tt>config_techlead.yaml</tt> (e.g.) that looks like so:
|
79
|
+
--- !ruby/object:Resume::ResumeConfig
|
80
|
+
achievement_tags:
|
81
|
+
- :lead
|
82
|
+
- :pm
|
83
|
+
- :developer
|
84
|
+
alt_tag: :architect
|
85
|
+
core_name: developer
|
86
|
+
include_untagged_achievements: true
|
87
|
+
max_achievements: 5
|
88
|
+
min_achievements: 3
|
89
|
+
primary_tag: :developer
|
90
|
+
3. Modify the contents to your liking based upon how you've created your database and taking into account the algorithm.
|
91
|
+
|
92
|
+
The algorithm works like this:
|
93
|
+
|
94
|
+
1. The achievements for a position are filtered first based upon <tt>achievement_tags</tt>. Anything not tagged with one of those tags is omitted. Untagged acheivements are included if you set <tt>include_untagged_achievements</tt> to true, otherwise they are not included
|
95
|
+
2. Anything tagged <tt>:major</tt> is included regardless of the above algorithm
|
96
|
+
3. If no achievement was tagged with the tag specified in <tt>primary_tag</tt>, all achievements tagged with the tag specified in <tt>alt_tag</tt> are included.
|
97
|
+
4. If the number of achievements is below <tt>min_achievements</tt>, random achievements are added to make the list the minimum size
|
98
|
+
5. If the number of achievements is above <tt>max_achievements</tt>, any achievement tagged <tt>:minor</tt> is removed
|
99
|
+
|
100
|
+
So, the way to take advantage of this is:
|
101
|
+
|
102
|
+
* Tag <tt>:major</tt> on anything you always want to appear; your proudest moments
|
103
|
+
* Tag <tt>:minor</tt> anything you don't care about but might need to fill out your resume
|
104
|
+
* Tag every achievement with fine-grained tags relevant to the positions you are pursuing
|
105
|
+
|
106
|
+
To use this filter, specify <tt>--filter position_filter --core techlead</tt> (or whatever you named your core and filter config file).
|
107
|
+
|
108
|
+
= TODO
|
109
|
+
|
110
|
+
* don't hardcode the skill categories
|
111
|
+
* make scaffolding more fine-grained
|
112
|
+
* Allow for user-generated templates
|
data/lib/formatter.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'erb'
|
2
|
+
module Resume
|
3
|
+
|
4
|
+
# Handles formatting the resume using ERB templates.
|
5
|
+
# Create an instance of this class, then call a method named for your template.
|
6
|
+
# In +templates_dir+, if a file is named Xyz.erb, formatter.Xyz will run the resume through
|
7
|
+
# the template. If an argument is passed to the method, the resulting file
|
8
|
+
# will be copied to the file named by the argument.
|
9
|
+
#
|
10
|
+
# formatter = Formatter.new(resume,"MyResume","/home/davec/templates")
|
11
|
+
# formatter.doc("DavesResume.doc")
|
12
|
+
#
|
13
|
+
# This will try to find +/home/davec/templates/doc.erb+ and use it to create your resume. The
|
14
|
+
# resulting file would be +MyResume.doc+ and that file would be copied to +DavesResume.doc+
|
15
|
+
class Formatter
|
16
|
+
TEMPLATES_DEFAULT = File.expand_path(File.dirname(__FILE__) + '/../templates')
|
17
|
+
# Create a new Formatter for a given resume
|
18
|
+
#
|
19
|
+
# +resume+:: The Resume object to format
|
20
|
+
# +resume_name+:: The basename of the files that will be output
|
21
|
+
# +templates_dir+:: location of your templates
|
22
|
+
def initialize(resume,resume_name="resume",templates_dir=TEMPLATES_DEFAULT)
|
23
|
+
@resume = resume
|
24
|
+
@resume_name = resume_name
|
25
|
+
@templates_dir = templates_dir
|
26
|
+
end
|
27
|
+
|
28
|
+
# Does the formatting based on the method name, if the template is found. Case matters
|
29
|
+
def method_missing(method,*args)
|
30
|
+
type = method.to_s
|
31
|
+
template_file = "#{@templates_dir}/#{type}.erb"
|
32
|
+
if (File.exists? template_file)
|
33
|
+
template = ""
|
34
|
+
File.open(template_file) do |input|
|
35
|
+
input.readlines.each() do |line|
|
36
|
+
template += line
|
37
|
+
end
|
38
|
+
end
|
39
|
+
type_template = ERB.new(template)
|
40
|
+
type_data = type_template.result(@resume.get_binding)
|
41
|
+
file_name = @resume_name + "." + type.downcase
|
42
|
+
File.open(@resume_name + "." + type.downcase,'w') do |file|
|
43
|
+
file.puts type_data
|
44
|
+
end
|
45
|
+
if args && !args.empty?
|
46
|
+
cp(file_name,args[0])
|
47
|
+
end
|
48
|
+
else
|
49
|
+
raise "There is no template for #{type}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/resume.rb
ADDED
@@ -0,0 +1,399 @@
|
|
1
|
+
require 'string_tags.rb'
|
2
|
+
|
3
|
+
module Resume
|
4
|
+
# The core elemens of the resume
|
5
|
+
class ResumeCore
|
6
|
+
# Contact info, such as name and address
|
7
|
+
attr_accessor :contact_info
|
8
|
+
# Headline statement (String)
|
9
|
+
attr_accessor :headline
|
10
|
+
# Summary of qualifications (String)
|
11
|
+
attr_accessor :summary
|
12
|
+
# The name of this resume core, for conditional creation
|
13
|
+
attr_accessor :name
|
14
|
+
|
15
|
+
def ResumeCore.scaffold
|
16
|
+
core = ResumeCore.new
|
17
|
+
core.contact_info = ContactInfo.scaffold
|
18
|
+
core.headline = "Sum up yourself here in this headline"
|
19
|
+
core.summary = "Provide a somewhat longer summary, in paragraph form, of why you are so awesome"
|
20
|
+
core
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# The entire resume
|
25
|
+
class Resume
|
26
|
+
# a ResumeCore object
|
27
|
+
attr_accessor :core
|
28
|
+
# Array of Job objects
|
29
|
+
attr_accessor :experience
|
30
|
+
# A SkillSet
|
31
|
+
attr_accessor :skills
|
32
|
+
# Array of Education objects
|
33
|
+
attr_accessor :education
|
34
|
+
# Array of Reference objects
|
35
|
+
attr_accessor :references
|
36
|
+
# Array of Sample objects
|
37
|
+
attr_accessor :samples
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@samples = Array.new
|
41
|
+
@references = Array.new
|
42
|
+
@education = Array.new
|
43
|
+
@experience = Array.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_binding
|
47
|
+
binding
|
48
|
+
end
|
49
|
+
|
50
|
+
def Resume.scaffold
|
51
|
+
resume = Resume.new
|
52
|
+
resume.core = ResumeCore.scaffold
|
53
|
+
resume.experience << Job.scaffold
|
54
|
+
resume.experience << Job.scaffold("Penetrode")
|
55
|
+
resume.skills = SkillSet.scaffold
|
56
|
+
resume.education << Education.scaffold
|
57
|
+
resume.education << Education.scaffold("Rhode Island School of Design")
|
58
|
+
resume.references << Reference.scaffold("John Q. Manager")
|
59
|
+
resume.references << Reference.scaffold("Jimmy Jones Bossman")
|
60
|
+
resume.samples << Sample.scaffold
|
61
|
+
resume.samples << Sample.scaffold
|
62
|
+
resume
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# A sample of work, avaiable via the internet
|
67
|
+
class Sample
|
68
|
+
attr_accessor :name
|
69
|
+
attr_accessor :url
|
70
|
+
|
71
|
+
def Sample.scaffold
|
72
|
+
sample = Sample.new
|
73
|
+
sample.name = "Name of this work sample"
|
74
|
+
sample.url = "http://www.google.com"
|
75
|
+
sample
|
76
|
+
end
|
77
|
+
|
78
|
+
def <=>(other_sample)
|
79
|
+
return 1 if !other_sample
|
80
|
+
@name <=> other_sample.name
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
# Represents a personal reference
|
85
|
+
class Reference
|
86
|
+
attr_accessor :name
|
87
|
+
attr_accessor :phone
|
88
|
+
attr_accessor :email
|
89
|
+
attr_accessor :company
|
90
|
+
attr_accessor :title
|
91
|
+
attr_accessor :relationship
|
92
|
+
|
93
|
+
def safe_email
|
94
|
+
return @email.gsub(/@/," at ").gsub(/\./," dot ")
|
95
|
+
end
|
96
|
+
|
97
|
+
def Reference.scaffold(name = nil)
|
98
|
+
ref = Reference.new
|
99
|
+
ref.name = name
|
100
|
+
ref.name = "Some Guy's Name" if !name
|
101
|
+
ref.phone = rand(10).to_s + rand(10).to_s + rand(10).to_s + "-555-1212"
|
102
|
+
ref.email = "someguy@someplace.com"
|
103
|
+
ref.company = "Some Place"
|
104
|
+
ref.relationship = "Boss"
|
105
|
+
ref.title = "VP, Widgets"
|
106
|
+
ref
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Represents all skills
|
111
|
+
class SkillSet
|
112
|
+
|
113
|
+
@@categories = {
|
114
|
+
:languages=> "Languages",
|
115
|
+
:apis=> "APIs",
|
116
|
+
:tools=>"Tools",
|
117
|
+
:databases=>"Databases",
|
118
|
+
:operating_systems=>"OSes"
|
119
|
+
}
|
120
|
+
|
121
|
+
@@category_order = [
|
122
|
+
:languages,
|
123
|
+
:apis,
|
124
|
+
:tools,
|
125
|
+
:databases,
|
126
|
+
:operating_systems,
|
127
|
+
]
|
128
|
+
|
129
|
+
def SkillSet.category_order; @@category_order; end
|
130
|
+
def SkillSet.categories; @@categories; end
|
131
|
+
# Returns the labels for the categories in order
|
132
|
+
def SkillSet.category_labels_order
|
133
|
+
labels = Array.new
|
134
|
+
@@category_order.each() { |c| labels << @@categories[c] }
|
135
|
+
labels
|
136
|
+
end
|
137
|
+
|
138
|
+
# Hash of category to skill objects
|
139
|
+
attr_accessor :skills
|
140
|
+
|
141
|
+
def initialize
|
142
|
+
@skills = Hash.new
|
143
|
+
end
|
144
|
+
|
145
|
+
def all_novice_skills
|
146
|
+
novice_skills = Array.new
|
147
|
+
skills.each() do |k,v|
|
148
|
+
novice_skills = novice_skills | v.select() { |x| x.experience_level == :novice }.sort().reverse()
|
149
|
+
end
|
150
|
+
novice_skills
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns a map of skill categories to arrays of skills
|
154
|
+
def non_novice_skills_by_category
|
155
|
+
non_novice = Hash.new
|
156
|
+
SkillSet.category_order.each() do |category|
|
157
|
+
non_novice[SkillSet.categories[category]] = skills[category].select() { |x| x.experience_level != :novice }.sort().reverse()
|
158
|
+
end
|
159
|
+
non_novice
|
160
|
+
end
|
161
|
+
|
162
|
+
def SkillSet.scaffold
|
163
|
+
rand_skills = {
|
164
|
+
:languages => "COBOL",
|
165
|
+
:apis => "MOTIF",
|
166
|
+
:tools => "Turbo Pascal",
|
167
|
+
:databases => "Sybase",
|
168
|
+
:operating_systems => "OS/2",
|
169
|
+
}
|
170
|
+
set = SkillSet.new
|
171
|
+
@@category_order.each() do |c|
|
172
|
+
set.skills[c] = Array.new
|
173
|
+
set.skills[c] << Skill.scaffold(rand_skills[c])
|
174
|
+
end
|
175
|
+
set
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Represents a skill, such as "Java"
|
180
|
+
class Skill
|
181
|
+
attr_accessor :name
|
182
|
+
attr_accessor :experience_level
|
183
|
+
attr_accessor :years_experience
|
184
|
+
|
185
|
+
def initialize(name,exp)
|
186
|
+
@name = name
|
187
|
+
@experience_level = exp
|
188
|
+
end
|
189
|
+
|
190
|
+
def <=>(other_skill)
|
191
|
+
return 1 if (other_skill == nil)
|
192
|
+
if (other_skill.experience_level == @experience_level)
|
193
|
+
if other_skill.years_experience
|
194
|
+
return @years_experience <=> other_skill.years_experience
|
195
|
+
elsif @years_experience
|
196
|
+
return 1
|
197
|
+
else
|
198
|
+
return 0
|
199
|
+
end
|
200
|
+
else
|
201
|
+
return 1 if (@experience_level == :expert)
|
202
|
+
return 1 if (@experience_level == :intermediate && other_skill.experience_level == :novice)
|
203
|
+
return -1;
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def Skill.scaffold(name=nil)
|
208
|
+
exp = {
|
209
|
+
0 => :novice,
|
210
|
+
1 => :intermdiate,
|
211
|
+
2 => :expert,
|
212
|
+
}
|
213
|
+
skill = Skill.new(name,exp[rand(3)])
|
214
|
+
skill.name = "EBCIDIC" if !name
|
215
|
+
skill.years_experience = rand(10) + 1
|
216
|
+
skill
|
217
|
+
end
|
218
|
+
|
219
|
+
def to_s
|
220
|
+
@name
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Represents a degree earned or other college-type educational experience
|
225
|
+
class Education
|
226
|
+
attr_accessor :name
|
227
|
+
attr_accessor :degree
|
228
|
+
attr_accessor :year_graduated
|
229
|
+
attr_accessor :major
|
230
|
+
attr_accessor :other_info
|
231
|
+
|
232
|
+
def Education.scaffold(name=nil)
|
233
|
+
ed = Education.new
|
234
|
+
ed.name = name
|
235
|
+
ed.name = "Degree Mill U" if !name
|
236
|
+
ed.degree = "Bachelor of Fine Arts"
|
237
|
+
ed.year_graduated = 1969
|
238
|
+
ed.major = "Underwater Basketweaving"
|
239
|
+
ed.other_info = "Thesis: Basketweaving simplified"
|
240
|
+
ed
|
241
|
+
end
|
242
|
+
|
243
|
+
def <=>(other_education)
|
244
|
+
return 1 if !other_education
|
245
|
+
return year_graduated <=> other_education.year_graduated
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Contact information, such as name, email, address
|
250
|
+
class ContactInfo
|
251
|
+
attr_accessor :name
|
252
|
+
attr_accessor :email
|
253
|
+
# An Address object
|
254
|
+
attr_accessor :address
|
255
|
+
attr_accessor :phone
|
256
|
+
|
257
|
+
def safe_email
|
258
|
+
return @email.gsub(/@/," at ").gsub(/\./," dot ")
|
259
|
+
end
|
260
|
+
|
261
|
+
def ContactInfo.scaffold
|
262
|
+
contact = ContactInfo.new
|
263
|
+
contact.name = "John J. Programmer"
|
264
|
+
contact.email = "lacky@whatyouneed.com"
|
265
|
+
contact.address = Address.scaffold
|
266
|
+
contact.phone = "202-555-1212"
|
267
|
+
contact
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
class Address
|
272
|
+
attr_accessor :street
|
273
|
+
attr_accessor :city
|
274
|
+
attr_accessor :state
|
275
|
+
attr_accessor :zip
|
276
|
+
|
277
|
+
def Address.scaffold
|
278
|
+
address = Address.new
|
279
|
+
address.street = "124 Any St, #666"
|
280
|
+
address.city = "Sometown"
|
281
|
+
address.state = "AZ"
|
282
|
+
address.zip = "94118"
|
283
|
+
address
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# A job that you had, i.e. a time working for a company
|
288
|
+
class Job
|
289
|
+
# Name of the company
|
290
|
+
attr_accessor :name
|
291
|
+
attr_accessor :date_range
|
292
|
+
# Location, e.g. San Francisco, VA
|
293
|
+
attr_accessor :location
|
294
|
+
# Array of Position objects
|
295
|
+
attr_accessor :positions
|
296
|
+
|
297
|
+
def initialize
|
298
|
+
@positions = Array.new
|
299
|
+
end
|
300
|
+
|
301
|
+
def Job.scaffold(name="Initech")
|
302
|
+
job = Job.new
|
303
|
+
job.name = name
|
304
|
+
job.date_range = DateRange.scaffold(2000,2003)
|
305
|
+
job.location = "San Francisco, CA"
|
306
|
+
job.positions << Position.scaffold("Lead Copy Boy",2001)
|
307
|
+
job.positions << Position.scaffold("Copy Boy",2000)
|
308
|
+
job
|
309
|
+
end
|
310
|
+
|
311
|
+
def <=>(other_position)
|
312
|
+
return 1 if !other_position
|
313
|
+
return 1 if !other_position.date_range
|
314
|
+
return date_range <=> other_position.date_range
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
#
|
319
|
+
|
320
|
+
# A position held within a job
|
321
|
+
class Position
|
322
|
+
# Job title
|
323
|
+
attr_accessor :title
|
324
|
+
attr_accessor :date_range
|
325
|
+
# Description of the position's requirements
|
326
|
+
attr_accessor :description
|
327
|
+
# Array of achievements
|
328
|
+
attr_accessor :achievements
|
329
|
+
|
330
|
+
def initialize
|
331
|
+
@achievements = Array.new
|
332
|
+
end
|
333
|
+
|
334
|
+
def Position.scaffold(name,year)
|
335
|
+
position = Position.new
|
336
|
+
position.title = name
|
337
|
+
#position.date_range = DateRange.new
|
338
|
+
#position.date_range.start_date = Date.civil(year,01,01);
|
339
|
+
#position.date_range.end_date = Date.civil(year,12,01);
|
340
|
+
position.description = "Enter in a description of the position you held"
|
341
|
+
position.achievements << "Enter your most important achievements first"
|
342
|
+
position.achievements << "Follow with additional ones, making sure to indicate the result of your actions"
|
343
|
+
position.achievements[0].tags = Array.new
|
344
|
+
position.achievements[0].tags << :major
|
345
|
+
position.achievements[0].tags << :architect
|
346
|
+
position.achievements[1].tags = Array.new
|
347
|
+
position.achievements[1].tags << :architect
|
348
|
+
position
|
349
|
+
end
|
350
|
+
|
351
|
+
def <=>(other_position)
|
352
|
+
return 1 if !other_position
|
353
|
+
return 1 if !other_position.date_range
|
354
|
+
return date_range <=> other_position.date_range
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
class DateRange
|
359
|
+
attr_accessor :start_date
|
360
|
+
attr_accessor :end_date
|
361
|
+
|
362
|
+
def DateRange.scaffold(year1,year2)
|
363
|
+
range = DateRange.new
|
364
|
+
range.start_date=Date.ordinal(year1,1)
|
365
|
+
range.end_date=Date.ordinal(year2,1)
|
366
|
+
range
|
367
|
+
end
|
368
|
+
|
369
|
+
def <=>(other_range)
|
370
|
+
return @start_date <=> other_range.start_date
|
371
|
+
end
|
372
|
+
|
373
|
+
def to_s
|
374
|
+
if (@end_date)
|
375
|
+
return "#{start_date.month}/#{start_date.year} - #{end_date.month}/#{end_date.year}"
|
376
|
+
else
|
377
|
+
return "#{start_date.month}/#{start_date.year} - present"
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
# Configuration for generating a resume
|
383
|
+
class ResumeConfig
|
384
|
+
# Primary achievement tag to make sure gets included
|
385
|
+
attr_accessor :primary_tag
|
386
|
+
# Alternate achievement tag to make sure gets included if the primary tag isn't used
|
387
|
+
attr_accessor :alt_tag
|
388
|
+
# List of tags to include
|
389
|
+
attr_accessor :achievement_tags
|
390
|
+
# Minimum number of achievements per position
|
391
|
+
attr_accessor :min_achievements
|
392
|
+
# Maximum number of achievements per position
|
393
|
+
attr_accessor :max_achievements
|
394
|
+
# If false, untagged achievements are ignore
|
395
|
+
attr_accessor :include_untagged_achievements
|
396
|
+
attr_accessor :core_name
|
397
|
+
end
|
398
|
+
|
399
|
+
end
|
data/lib/serializer.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'resume'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
include FileUtils
|
6
|
+
module Resume
|
7
|
+
# Serializes a resume to/from YAML
|
8
|
+
class Serializer
|
9
|
+
|
10
|
+
attr_accessor :experience_prefix
|
11
|
+
attr_accessor :education_prefix
|
12
|
+
attr_accessor :references_prefix
|
13
|
+
attr_accessor :samples_prefix
|
14
|
+
attr_accessor :core_prefix
|
15
|
+
attr_accessor :skills_prefix
|
16
|
+
attr_accessor :core_to_use
|
17
|
+
attr_accessor :filter_file
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@experience_prefix='experience'
|
21
|
+
@education_prefix='education'
|
22
|
+
@references_prefix='references'
|
23
|
+
@samples_prefix='samples'
|
24
|
+
@core_prefix='resume'
|
25
|
+
@skills_prefix='skills'
|
26
|
+
end
|
27
|
+
|
28
|
+
# Loads the resume artifacts from the given directory
|
29
|
+
def load(dir)
|
30
|
+
if (!File.exists?(dir))
|
31
|
+
raise "#{dir} doesn't exist"
|
32
|
+
end
|
33
|
+
|
34
|
+
resume = Resume.new
|
35
|
+
cores = read_cores(dir,core_prefix)
|
36
|
+
if (cores.size > 1)
|
37
|
+
if (core_to_use)
|
38
|
+
cores.each() { |core| resume.core = core if core.name == core_to_use }
|
39
|
+
else
|
40
|
+
raise "You must specify a core resume to use since there are #{cores.size} of them"
|
41
|
+
end
|
42
|
+
else
|
43
|
+
resume.core = cores[0]
|
44
|
+
end
|
45
|
+
raise "#{core_to_use} didn't match any resumes" if !resume.core
|
46
|
+
config_filename = "#{dir}/config_#{core_to_use}.yaml"
|
47
|
+
config = File.open(config_filename) { |yf| YAML::load( yf ) } if File.exists?(config_filename)
|
48
|
+
resume.skills = File.open( "#{dir}/#{skills_prefix}.yaml" ) { |yf| YAML::load( yf ) } if File.exists?("#{dir}/#{skills_prefix}.yaml")
|
49
|
+
resume.experience = read(dir,experience_prefix)
|
50
|
+
warn "No experience detected" if resume.experience.size == 0
|
51
|
+
resume.education = read(dir,education_prefix)
|
52
|
+
resume.references = read(dir,references_prefix)
|
53
|
+
resume.samples = read(dir,samples_prefix)
|
54
|
+
resume.experience.sort!.reverse!
|
55
|
+
resume.education.sort!.reverse!
|
56
|
+
resume.samples.sort!.reverse!
|
57
|
+
resume.experience.each() do |xp|
|
58
|
+
xp.positions.each() do |position|
|
59
|
+
if (filter_file)
|
60
|
+
require filter_file
|
61
|
+
if (FILTER.respond_to? :filter_config=)
|
62
|
+
FILTER.filter_config=config
|
63
|
+
end
|
64
|
+
position.achievements = FILTER.filter_achievements(position.achievements)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
xp.positions.sort!.reverse!
|
68
|
+
end
|
69
|
+
return resume
|
70
|
+
end
|
71
|
+
|
72
|
+
def read(dir,base_name,allow_prefix_only=false)
|
73
|
+
list = Array.new
|
74
|
+
files = Dir.glob("#{dir}/#{base_name}_*.yaml")
|
75
|
+
if (allow_prefix_only)
|
76
|
+
prefix_only_name = "#{dir}/#{base_name}.yaml";
|
77
|
+
files << prefix_only_name if File.exists? prefix_only_name
|
78
|
+
end
|
79
|
+
files.each do |file|
|
80
|
+
list << File.open( file ) { |yf| YAML::load( yf ) }
|
81
|
+
end
|
82
|
+
list
|
83
|
+
end
|
84
|
+
|
85
|
+
def read_cores(dir,base_name)
|
86
|
+
return read(dir,base_name,true)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Stores the resume artifacts to the given directory.
|
90
|
+
def store(dir,resume)
|
91
|
+
if File.exists?(dir)
|
92
|
+
rm(Dir.glob("#{dir}/*.yaml"))
|
93
|
+
else
|
94
|
+
mkdir(dir)
|
95
|
+
end
|
96
|
+
File.open("#{dir}/resume.yaml",'w') { |out| YAML::dump(resume.core,out) }
|
97
|
+
dump(dir,"experience",resume.experience)
|
98
|
+
dump(dir,"education",resume.education)
|
99
|
+
dump(dir,"reference",resume.references)
|
100
|
+
dump(dir,"samples",resume.samples)
|
101
|
+
File.open("#{dir}/skills.yaml",'w') { |out| YAML::dump(resume.skills,out) }
|
102
|
+
end
|
103
|
+
|
104
|
+
def dump(dir,base_name,items)
|
105
|
+
if (items && !items.empty?)
|
106
|
+
count = 1
|
107
|
+
items.each() do |item|
|
108
|
+
filename = base_name + "_"
|
109
|
+
if (item.respond_to? :name)
|
110
|
+
filename += item.name.gsub(/\//,"_").gsub(/\s/,"_")
|
111
|
+
else
|
112
|
+
filename += count.to_s
|
113
|
+
end
|
114
|
+
filename += ".yaml"
|
115
|
+
File.open("#{dir}/#{filename}",'w') { |out| YAML::dump(item,out) }
|
116
|
+
count += 1
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/templates/HTML.erb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Resume of <%= @core.contact_info.name %></title>
|
4
|
+
</head>
|
5
|
+
<body>
|
6
|
+
<h1><%= @core.contact_info.name %></h1>
|
7
|
+
|
8
|
+
<ul>
|
9
|
+
<li><b>Address:</b> <%= @core.contact_info.address.city %>, <%= @core.contact_info.address.state %> <%= @core.contact_info.address.zip %>
|
10
|
+
<li><b>Phone:</b> <%= @core.contact_info.phone %>
|
11
|
+
<li><b>Email:</b> <%= @core.contact_info.safe_email %>
|
12
|
+
</ul>
|
13
|
+
<hr>
|
14
|
+
<p style="font-size: 110%; font-weight: bold">
|
15
|
+
<%= @core.headline %>
|
16
|
+
</p>
|
17
|
+
|
18
|
+
<h2>Summary</h2>
|
19
|
+
<p style="font-style:italic">
|
20
|
+
<%= @core.summary %>
|
21
|
+
</p>
|
22
|
+
<h2>Skills</h2>
|
23
|
+
<table>
|
24
|
+
<%
|
25
|
+
non_novice_skills = @skills.non_novice_skills_by_category
|
26
|
+
SkillSet.category_labels_order.each() do |category|
|
27
|
+
these_skills = non_novice_skills[category]
|
28
|
+
%>
|
29
|
+
<tr><td><b><%= category %></b></td><td><%= these_skills.join(", ") %></td></tr><% end %>
|
30
|
+
<% if (!@skills.all_novice_skills.empty?) %><tr><td><i>Some experience with</i></td>
|
31
|
+
<td><%= @skills.all_novice_skills.join(", ") %></td></tr><% end %>
|
32
|
+
</table>
|
33
|
+
|
34
|
+
<h2>Experience</h2>
|
35
|
+
|
36
|
+
<% @experience.each() do |job| %>
|
37
|
+
<h3><%= job.name %>(<%= job.location %>) <%= (!job.positions || job.positions.size != 1) ? "<i>#{job.date_range.to_s}</i>" : '' %></h3>
|
38
|
+
<% job.positions.each() do |position| %>
|
39
|
+
<h4><%= position.title %></h4>
|
40
|
+
<i><%= position.date_range.to_s %></i>
|
41
|
+
<p>
|
42
|
+
<b>Description:</b><%= position.description %>
|
43
|
+
</p>
|
44
|
+
<p>
|
45
|
+
Key Achievements:
|
46
|
+
<ul>
|
47
|
+
<% position.achievements.each() do |achievement| %>
|
48
|
+
<li><%= achievement %></li>
|
49
|
+
<% end %>
|
50
|
+
</ul>
|
51
|
+
</p>
|
52
|
+
<% end %>
|
53
|
+
<% end %>
|
54
|
+
|
55
|
+
<h2>Education</h2>
|
56
|
+
<% @education.each() do |school| %>
|
57
|
+
<h3><%= school.name %> - <%= school.degree %>, <%= school.major %>, <%= school.year_graduated %></h3>
|
58
|
+
<% if school.other_info %>
|
59
|
+
<p><%= school.other_info %></p>
|
60
|
+
<% end %><% end %>
|
61
|
+
<% if @samples && !@samples.empty? %>
|
62
|
+
<h2>Samples</h2>
|
63
|
+
<ul>
|
64
|
+
<% @samples.each() do |sample| %>
|
65
|
+
<li><a href="<%= sample.url %>"><%= sample.name %></a></li>
|
66
|
+
<% end %>
|
67
|
+
<% end %>
|
68
|
+
</ul>
|
69
|
+
</body>
|
70
|
+
</html>
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# <%= @core.contact_info.name %>
|
2
|
+
|
3
|
+
+ **Address:** <%= @core.contact_info.address.city %>, <%= @core.contact_info.address.state %> <%= @core.contact_info.address.zip %>
|
4
|
+
+ **Phone:** <%= @core.contact_info.phone %>
|
5
|
+
+ **Email:** <%= @core.contact_info.safe_email %>
|
6
|
+
|
7
|
+
* * *
|
8
|
+
**<%= @core.headline %>**
|
9
|
+
## Summary
|
10
|
+
|
11
|
+
<%= @core.summary %>
|
12
|
+
## Skills
|
13
|
+
|
14
|
+
<%
|
15
|
+
non_novice_skills = @skills.non_novice_skills_by_category
|
16
|
+
SkillSet.category_labels_order.each() do |category|
|
17
|
+
these_skills = non_novice_skills[category]
|
18
|
+
%>
|
19
|
+
+ **<%= category %>**: <%= these_skills.join(", ") %><%end %>
|
20
|
+
<% if (!@skills.all_novice_skills.empty?) %>+ *Some experience with*: <%= @skills.all_novice_skills.join(", ") %><% end %>
|
21
|
+
|
22
|
+
## Experience
|
23
|
+
|
24
|
+
<% @experience.each() do |job| %>
|
25
|
+
### <%= job.name %>(<%= job.location %>) <%= (!job.positions || job.positions.size != 1) ? "_#{job.date_range.to_s}_" : '' %>
|
26
|
+
<% job.positions.each() do |position| %>
|
27
|
+
#### <%= position.title %>
|
28
|
+
_<%= position.date_range.to_s %>_
|
29
|
+
|
30
|
+
_Description:_ <%= position.description %>
|
31
|
+
|
32
|
+
Key Achievements:
|
33
|
+
<% position.achievements.each() do |achievement| %>
|
34
|
+
+ <%= achievement %><% end %>
|
35
|
+
<% end %>
|
36
|
+
<% end %>
|
37
|
+
|
38
|
+
## Education
|
39
|
+
<% @education.each() do |school| %>
|
40
|
+
### <%= school.name %> - <%= school.degree %>, <%= school.major %>, <%= school.year_graduated %>
|
41
|
+
<% if school.other_info %>
|
42
|
+
<%= school.other_info %>
|
43
|
+
<% end %><% end %>
|
44
|
+
|
45
|
+
<% if @samples && !@samples.empty? %>
|
46
|
+
## Samples
|
47
|
+
<% @samples.each() do |sample| %>
|
48
|
+
* [<%= sample.name %>](<%= sample.url %>)<% end %>
|
49
|
+
<% end %>
|
data/templates/RTF.erb
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
{\rtf1\ansi\deff0\deflang2057\plain\fs24\fet1
|
2
|
+
{\fonttbl
|
3
|
+
{\f0\fswiss Georgia;}
|
4
|
+
{\f1\fswiss Helvetica;}
|
5
|
+
}
|
6
|
+
{\info
|
7
|
+
{\createim\yr2008\mo10\dy26\hr14\min58}
|
8
|
+
}
|
9
|
+
{\footer
|
10
|
+
{\i\fs16
|
11
|
+
Resume of <%= @core.contact_info.name %>, generated by http://github.com/davetron5000/daves-resume
|
12
|
+
}}
|
13
|
+
\paperw12247\paperh15819\margl1800\margr1440\margt1440\margb1440
|
14
|
+
{\pard\ql\li-360
|
15
|
+
{\b\f1\fs38
|
16
|
+
<%= @core.contact_info.name %>
|
17
|
+
}
|
18
|
+
\par}
|
19
|
+
{\pard\ql
|
20
|
+
<%= @core.contact_info.address.city %>, <%= @core.contact_info.address.state %> <%= @core.contact_info.address.zip %>
|
21
|
+
\par}
|
22
|
+
{\pard\ql
|
23
|
+
<%= @core.contact_info.phone %>
|
24
|
+
\par}
|
25
|
+
{\pard\ql
|
26
|
+
<%= @core.contact_info.email %>
|
27
|
+
\par}
|
28
|
+
{\pard
|
29
|
+
|
30
|
+
\par}
|
31
|
+
{\pard\b\fs24
|
32
|
+
<%= @core.headline %>
|
33
|
+
\par}
|
34
|
+
{\pard
|
35
|
+
|
36
|
+
\par}
|
37
|
+
{\pard\ql\li-360
|
38
|
+
{\b\f1\fs34
|
39
|
+
Summary
|
40
|
+
}
|
41
|
+
\par}
|
42
|
+
{\pard
|
43
|
+
|
44
|
+
\par}
|
45
|
+
{\pard\ql
|
46
|
+
{\i\fs22
|
47
|
+
<%= @core.summary %>
|
48
|
+
}
|
49
|
+
\par}
|
50
|
+
{\pard
|
51
|
+
|
52
|
+
\par}
|
53
|
+
{\pard\ql\li-360
|
54
|
+
{\b\f1\fs34
|
55
|
+
Skills
|
56
|
+
}
|
57
|
+
\par}
|
58
|
+
{\pard
|
59
|
+
|
60
|
+
\par}
|
61
|
+
<%
|
62
|
+
non_novice_skills = @skills.non_novice_skills_by_category
|
63
|
+
SkillSet.category_labels_order.each() do |category|
|
64
|
+
these_skills = non_novice_skills[category]
|
65
|
+
%>
|
66
|
+
\trowd\tgraph100
|
67
|
+
\cellx1440
|
68
|
+
\cellx8640
|
69
|
+
\pard\intbl
|
70
|
+
{\b\fs18
|
71
|
+
<%= category %>
|
72
|
+
}
|
73
|
+
\cell
|
74
|
+
\pard\intbl
|
75
|
+
{\fs18
|
76
|
+
<%= these_skills.collect() { |item| item = item.experience_level == :expert ? "\\b #{item.to_s} \\plain\\fs18" : item}.join(", ") %>
|
77
|
+
}
|
78
|
+
\cell
|
79
|
+
\row
|
80
|
+
<% end %>
|
81
|
+
<% if (!@skills.all_novice_skills.empty?) %>
|
82
|
+
\trowd\tgraph100
|
83
|
+
\cellx1440
|
84
|
+
\cellx8640
|
85
|
+
\pard\intbl
|
86
|
+
{\b\fs18
|
87
|
+
Exposure to:
|
88
|
+
}
|
89
|
+
\cell
|
90
|
+
\pard\intbl
|
91
|
+
{\fs18
|
92
|
+
<%= @skills.all_novice_skills.join(", ") %>
|
93
|
+
}
|
94
|
+
\cell
|
95
|
+
\row
|
96
|
+
<% end %>
|
97
|
+
{\pard
|
98
|
+
|
99
|
+
\par}
|
100
|
+
{\pard\ql\li-360
|
101
|
+
{\b\f1\fs34
|
102
|
+
Experience
|
103
|
+
}
|
104
|
+
\par}
|
105
|
+
{\pard
|
106
|
+
|
107
|
+
\par}
|
108
|
+
|
109
|
+
<% @experience.each() do |job| %>
|
110
|
+
{\pard\ql\li-180
|
111
|
+
{\b\fs24
|
112
|
+
<%= job.name %>
|
113
|
+
}
|
114
|
+
\par}
|
115
|
+
{\pard\ql\li-180
|
116
|
+
<%= job.location %> - <%= job.date_range.to_s %>
|
117
|
+
\par}
|
118
|
+
<% job.positions.each() do |position| %>
|
119
|
+
{\pard\fs22
|
120
|
+
{\ul
|
121
|
+
<%= position.title %> <%= position.date_range.to_s %>
|
122
|
+
}
|
123
|
+
\par}
|
124
|
+
{\pard
|
125
|
+
|
126
|
+
\par}
|
127
|
+
{\pard
|
128
|
+
{\i
|
129
|
+
{\fs18
|
130
|
+
{\b
|
131
|
+
Description:
|
132
|
+
}
|
133
|
+
<%= position.description %>
|
134
|
+
}
|
135
|
+
}
|
136
|
+
\par}
|
137
|
+
{\pard
|
138
|
+
|
139
|
+
\par}
|
140
|
+
{\pard
|
141
|
+
{\fs18
|
142
|
+
{\b
|
143
|
+
Key Achievements:
|
144
|
+
}
|
145
|
+
}
|
146
|
+
\par}
|
147
|
+
<% position.achievements.each() do |achievement| %>
|
148
|
+
{\pard\ql\li360\fi-360\tqr\tx720\tqr\tx1080\tqr\tx1440\tqr\tx1800\tqr\tx2160\tqr\tx2520\tqr\tx2880\tqr\tx3240\tqr\tx3600\tqr\tx3960
|
149
|
+
{\fs18
|
150
|
+
- <%= achievement %>
|
151
|
+
}
|
152
|
+
\par}
|
153
|
+
<% end %>
|
154
|
+
{\pard
|
155
|
+
|
156
|
+
\par}
|
157
|
+
<% end %>
|
158
|
+
<% end %>
|
159
|
+
{\pard
|
160
|
+
|
161
|
+
\par}
|
162
|
+
{\pard\ql\li-360
|
163
|
+
{\b\f1\fs34
|
164
|
+
Education
|
165
|
+
}
|
166
|
+
\par}
|
167
|
+
{\pard
|
168
|
+
|
169
|
+
\par}
|
170
|
+
<% @education.each() do |school| %>
|
171
|
+
{\pard\b\fs24
|
172
|
+
<%= school.name %> - <%= school.degree %>, <%= school.major %>, <%= school.year_graduated %>
|
173
|
+
\par}
|
174
|
+
{\pard\ql\li360
|
175
|
+
<% if school.other_info %>
|
176
|
+
{\i\fs18
|
177
|
+
<%= school.other_info %>
|
178
|
+
}
|
179
|
+
<% end %>
|
180
|
+
\par}
|
181
|
+
<% end %>
|
182
|
+
}
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: davetron5000-daves-resume
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Copeland
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-10-27 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: commandline
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.7.10
|
23
|
+
version:
|
24
|
+
description:
|
25
|
+
email: davidcopeland@naildrivin5.com
|
26
|
+
executables:
|
27
|
+
- dr
|
28
|
+
- dr-format
|
29
|
+
- dr-scaffold
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files:
|
33
|
+
- lib/README.rdoc
|
34
|
+
files:
|
35
|
+
- ext/make_commandline_gemproof.rb
|
36
|
+
- ext/position_filter.rb
|
37
|
+
- ext/string_tags.rb
|
38
|
+
- lib/formatter.rb
|
39
|
+
- lib/resume.rb
|
40
|
+
- lib/serializer.rb
|
41
|
+
- templates/HTML.erb
|
42
|
+
- templates/Markdown.erb
|
43
|
+
- templates/RTF.erb
|
44
|
+
- lib/README.rdoc
|
45
|
+
has_rdoc: true
|
46
|
+
homepage: http://www.naildrivin5.com
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options:
|
49
|
+
- --title
|
50
|
+
- Dave's Resume Build and Formatter
|
51
|
+
- --main
|
52
|
+
- lib/README.rdoc
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
- ext
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
version:
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.2.0
|
73
|
+
signing_key:
|
74
|
+
specification_version: 2
|
75
|
+
summary: Command Line tools to manage your resume and generate it in various formats
|
76
|
+
test_files: []
|
77
|
+
|