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 ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/ruby
2
+
3
+ COMMAND = ARGV.shift
4
+
5
+ cli = "dr-#{COMMAND}"
6
+
7
+ if (!system(cli,*ARGV))
8
+ cli = "bin/" + cli
9
+ if (!system(cli,*ARGV))
10
+ puts "Couldn't find #{cli}"
11
+ exit -1
12
+ end
13
+ end
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
@@ -0,0 +1,4 @@
1
+ class String
2
+ # A list of arbitrary tags for this string (as symbols)
3
+ attr_accessor :tags
4
+ end
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
@@ -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
+