virgild-resumetools 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,231 @@
1
+ #--
2
+ # Copyright (c) 2009 Virgil Dimaguila
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person
5
+ # obtaining a copy of this software and associated documentation
6
+ # files (the "Software"), to deal in the Software without
7
+ # restriction, including without limitation the rights to use,
8
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the
10
+ # Software is furnished to do so, subject to the following
11
+ # conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ # OTHER DEALINGS IN THE SOFTWARE.
24
+ #++
25
+
26
+ module ResumeTools
27
+
28
+ # = Resume
29
+ # Represents a single resume document
30
+ class Resume
31
+
32
+ # The title of the resume
33
+ attr_accessor :title
34
+
35
+ # The name (or label) for this resume
36
+ attr_accessor :name
37
+
38
+ # The full name of the subject of this resume
39
+ attr_accessor :full_name
40
+
41
+ # The subject's URL
42
+ attr_accessor :url
43
+
44
+ # The subject's e-mail address
45
+ attr_accessor :email
46
+
47
+ # The subject's telephone number
48
+ attr_accessor :telephone
49
+
50
+ # First line of the address
51
+ attr_accessor :address1
52
+
53
+ # Second line of the address
54
+ attr_accessor :address2
55
+
56
+ # The resume's sections
57
+ attr_reader :sections
58
+
59
+ # Creates a +Resume+ and passes it to the given block. Returns the created +Resume+.
60
+ def self.build(&block)
61
+ resume = self.new
62
+ yield resume
63
+ resume
64
+ end
65
+
66
+ # Creates a new +Resume+ with the given properties
67
+ def initialize(props={})
68
+ @title = props[:title] || ""
69
+ @name = props[:name] || "Unnamed Resume"
70
+ @full_name = props[:full_name] || ""
71
+ @url = props[:url] || ""
72
+ @email = props[:email] || ""
73
+ @telephone = props[:telephone] || ""
74
+ @address1 = props[:address1] || ""
75
+ @address2 = props[:address2] || ""
76
+ @sections = Array.new
77
+ end
78
+
79
+ # Add section
80
+ def add_section(section)
81
+ self.sections << section
82
+ self
83
+ end
84
+
85
+ # Create new section and add to sections
86
+ def create_section(props={}, &block)
87
+ section = Section.new(props)
88
+ yield section if block_given?
89
+ self.add_section(section)
90
+ self
91
+ end
92
+
93
+ #
94
+ def has_url?
95
+ !self.url.blank?
96
+ end
97
+
98
+ #
99
+ def has_email?
100
+ !self.email.blank?
101
+ end
102
+
103
+ #
104
+ def has_telephone?
105
+ !self.telephone.blank?
106
+ end
107
+
108
+ #
109
+ def has_address1?
110
+ !self.address1.blank?
111
+ end
112
+
113
+ #
114
+ def has_address2?
115
+ !self.address2.blank?
116
+ end
117
+
118
+ end
119
+
120
+
121
+ # = Section
122
+ # Represents a section in the resume
123
+ class Section
124
+ # Section title
125
+ attr_accessor :title
126
+
127
+ # Section paragraph
128
+ attr_accessor :para
129
+
130
+ # List of periods
131
+ attr_reader :periods
132
+
133
+ # List of items
134
+ attr_reader :items
135
+
136
+ #
137
+ def initialize(props={})
138
+ @title = props[:title] || ""
139
+ @para = props[:para] || ""
140
+ @items = Array.new
141
+ @periods = Array.new
142
+ end
143
+
144
+ # Creates a period and adds it to the list
145
+ def create_period(props={}, &block)
146
+ period = Period.new(props)
147
+ yield period if block_given?
148
+ self.add_period(period)
149
+ self
150
+ end
151
+
152
+ # Adds a period
153
+ def add_period(period)
154
+ self.periods << period
155
+ end
156
+
157
+ # Ads an item
158
+ def add_item(item)
159
+ self.items << item
160
+ end
161
+
162
+ # Creates an item and adds it to the list
163
+ def create_item(props={}, &block)
164
+ item = Item.new(props)
165
+ yield item if block_given?
166
+ self.add_item(item)
167
+ self
168
+ end
169
+ end
170
+
171
+
172
+ # = Period
173
+ # Represents a period in the section
174
+ class Period
175
+ # Period title
176
+ attr_accessor :title
177
+
178
+ # Period location
179
+ attr_accessor :location
180
+
181
+ # Period organization
182
+ attr_accessor :organization
183
+
184
+ # Period start date
185
+ attr_accessor :dtstart
186
+
187
+ # Period end date
188
+ attr_accessor :dtend
189
+
190
+ # List of items
191
+ attr_reader :items
192
+
193
+ #
194
+ def initialize(props={})
195
+ @title = props[:title] || ""
196
+ @location = props[:location] || ""
197
+ @organization = props[:organization] || ""
198
+ @dtstart = props[:dtstart] || nil
199
+ @dtend = props[:dtend] || nil
200
+ @items = Array.new
201
+ end
202
+
203
+ # Adds an item
204
+ def add_item(item)
205
+ self.items << item
206
+ end
207
+
208
+ # Creates an item and adds it to the list
209
+ def create_item(props={}, &block)
210
+ item = Item.new(props)
211
+ yield item if block_given?
212
+ self.add_item(item)
213
+ item
214
+ end
215
+ end
216
+
217
+
218
+ # = Item
219
+ # Represents an item in a period or section. Items are usually
220
+ # bulleted items that are listed in order
221
+ class Item
222
+ # The item text
223
+ attr_accessor :text
224
+
225
+ #
226
+ def initialize(props={})
227
+ @text = props[:text] || ""
228
+ end
229
+ end
230
+
231
+ end
@@ -0,0 +1,107 @@
1
+ #--
2
+ # Copyright (c) 2009 Virgil Dimaguila
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person
5
+ # obtaining a copy of this software and associated documentation
6
+ # files (the "Software"), to deal in the Software without
7
+ # restriction, including without limitation the rights to use,
8
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the
10
+ # Software is furnished to do so, subject to the following
11
+ # conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ # OTHER DEALINGS IN THE SOFTWARE.
24
+ #++
25
+
26
+ module ResumeTools
27
+ module TextReader
28
+ class ParseException < Exception
29
+ end
30
+
31
+ module ClassMethods
32
+ require 'treetop'
33
+
34
+ Treetop.load File.join(File.dirname(__FILE__), '..', 'grammars', 'resume.treetop')
35
+
36
+ # Builds a new Resume instance from text
37
+ def from_text(text)
38
+ parser = ::ResumeParser.new
39
+ result = parser.parse(text)
40
+
41
+ if result.nil?
42
+ raise ParseException.new(parser.failure_reason)
43
+ end
44
+
45
+ resume = ResumeTools::Resume.new
46
+ current_section = nil
47
+ current_period = nil
48
+
49
+ result.elements.each do |element|
50
+ case element.data_type
51
+ when :item
52
+ item = ::ResumeTools::Item.new(:text => element.value)
53
+ if current_period
54
+ current_period.add_item(item)
55
+ elsif current_section
56
+ current_section.add_item(item)
57
+ end
58
+ when :period
59
+ current_period = ::ResumeTools::Period.new(:title => element.value)
60
+ current_section.add_period(current_period) if current_section
61
+ when :section
62
+ current_period = nil # Reset period
63
+ current_section = ::ResumeTools::Section.new(:title => element.value)
64
+ resume.add_section(current_section)
65
+ when :period_location
66
+ current_period.location = element.value if current_period
67
+ when :period_organization
68
+ current_period.organization = element.value if current_period
69
+ when :period_dates
70
+ if current_period
71
+ dates = element.value.split("to", 2).map { |d| d.strip }
72
+ if dates.length == 1
73
+ current_period.dtend = dates[0]
74
+ current_period.dtstart = nil
75
+ elsif dates.length == 2
76
+ current_period.dtstart = dates[0]
77
+ current_period.dtend = dates[1]
78
+ end
79
+ end
80
+ when :paragraph
81
+ current_section.para = element.value if current_section
82
+ when :contact_name
83
+ resume.full_name = element.value
84
+ when :contact_telephone
85
+ resume.telephone = element.value
86
+ when :contact_email
87
+ resume.email = element.value
88
+ when :contact_address
89
+ if resume.address1.blank?
90
+ resume.address1 = element.value
91
+ elsif resume.address2.blank?
92
+ resume.address2 = element.value
93
+ end
94
+ when :contact_url
95
+ resume.url = element.value
96
+ end
97
+ end
98
+
99
+ resume
100
+ end
101
+ end
102
+ end
103
+
104
+ class << Resume
105
+ include TextReader::ClassMethods
106
+ end
107
+ end
@@ -0,0 +1,35 @@
1
+ #--
2
+ # Resume Tools, Copyright (c) 2009 Virgil Dimaguila
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person
5
+ # obtaining a copy of this software and associated documentation
6
+ # files (the "Software"), to deal in the Software without
7
+ # restriction, including without limitation the rights to use,
8
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the
10
+ # Software is furnished to do so, subject to the following
11
+ # conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ # OTHER DEALINGS IN THE SOFTWARE.
24
+ #++
25
+
26
+ unless defined? ResumeTools::VERSION
27
+ module ResumeTools
28
+ module VERSION
29
+ MAJOR = 0
30
+ MINOR = 2
31
+ TINY = 0
32
+ STRING = [MAJOR, MINOR, TINY].join('.')
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ #--
2
+ # Copyright (c) 2009 Virgil Dimaguila
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person
5
+ # obtaining a copy of this software and associated documentation
6
+ # files (the "Software"), to deal in the Software without
7
+ # restriction, including without limitation the rights to use,
8
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the
10
+ # Software is furnished to do so, subject to the following
11
+ # conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ # OTHER DEALINGS IN THE SOFTWARE.
24
+ #++
25
+
26
+ $:.unshift(File.dirname(__FILE__))
27
+
28
+ require "extlib"
29
+ require "uuidtools"
30
+
31
+ require "resumetools/version"
32
+ require "resume/resume"
33
+ require "resume/text_reader"
34
+ require "resume/pdf"
@@ -0,0 +1,93 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+ require "treetop"
3
+
4
+ Treetop.load File.join(File.dirname(__FILE__), "../lib/grammars/resume.treetop")
5
+
6
+ describe "Resume Grammar" do
7
+ before(:all) do
8
+ resume_file = "sample.resume"
9
+ @parser = ResumeParser.new
10
+ @resume_txt = File.read(File.join(File.dirname(__FILE__), "../examples/#{resume_file}"))
11
+ end
12
+
13
+ it "should have a parser" do
14
+ @parser.should be_instance_of(ResumeParser)
15
+ end
16
+
17
+ describe "after parsing sample file" do
18
+ def elements_with_type(type)
19
+ @elements.reject { |e| e.data_type != type }
20
+ end
21
+
22
+ before(:all) do
23
+ @result = @parser.parse(@resume_txt)
24
+ @elements = @result.elements
25
+ end
26
+
27
+ it "should parse our file" do
28
+ @result.should_not be_nil
29
+ end
30
+
31
+ it "should have 33 items" do
32
+ elements_with_type(:item).length.should == 33
33
+ end
34
+
35
+ it "should have 2 paragraphs" do
36
+ elements_with_type(:paragraph).length.should == 2
37
+ end
38
+
39
+ it "should have 7 sections" do
40
+ elements_with_type(:section).length.should == 7
41
+ end
42
+
43
+ it "should have contact name" do
44
+ elements_with_type(:contact_name).length.should == 1
45
+ elements_with_type(:contact_name)[0].value.should == "Thomas B. Seeker"
46
+ end
47
+
48
+ it "should have contact telephone" do
49
+ elements_with_type(:contact_telephone).length.should == 1
50
+ elements_with_type(:contact_telephone)[0].value.should == "(410) 555-1212"
51
+ end
52
+
53
+ it "should have contact addresses" do
54
+ elements_with_type(:contact_address).length.should == 2
55
+ elements_with_type(:contact_address)[0].value.should == "1234 Northern Star Circle"
56
+ elements_with_type(:contact_address)[1].value.should == "Baltimore, MD 12345"
57
+ end
58
+
59
+ it "should have contact e-mail" do
60
+ elements_with_type(:contact_email).length.should == 1
61
+ elements_with_type(:contact_email)[0].value.should == "seeker@nettcom.com"
62
+ end
63
+
64
+ it "should have contact URL" do
65
+ elements_with_type(:contact_url).length.should == 1
66
+ elements_with_type(:contact_url)[0].value.should == "http://nettcom.com/tseeker"
67
+ end
68
+
69
+ it "should have 5 periods" do
70
+ elements_with_type(:period).length.should == 5
71
+ end
72
+
73
+ it "should have 5 period organizations" do
74
+ elements_with_type(:period_organization).length.should == 5
75
+ elements_with_type(:period_organization).map { |o| o.value }.should == [
76
+ "Computer Engineering Corporation",
77
+ "Business Consultants, Inc.",
78
+ "US Army Infantry",
79
+ "Air Force Institute of Technology (AFIT)",
80
+ "University of California, Los Angeles (UCLA)"
81
+ ]
82
+ end
83
+
84
+ it "should have 3 period locations" do
85
+ elements_with_type(:period_location).length.should == 3
86
+ end
87
+
88
+ it "should have 3 period dates" do
89
+ elements_with_type(:period_dates).length.should == 3
90
+ end
91
+ end
92
+
93
+ end
@@ -0,0 +1,61 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe "Creating a resume from a text" do
4
+ before do
5
+ sample = File.join(File.dirname(__FILE__), '..', 'examples', 'sample.resume')
6
+ text = File.read(sample)
7
+ @resume = ResumeTools::Resume.from_text(text)
8
+ end
9
+
10
+ it "should create a resume" do
11
+ @resume.should be_instance_of(ResumeTools::Resume)
12
+ end
13
+
14
+ it "should have the correct contact info" do
15
+ @resume.full_name.should == "Thomas B. Seeker"
16
+ @resume.address1.should == "1234 Northern Star Circle"
17
+ @resume.address2.should == "Baltimore, MD 12345"
18
+ @resume.telephone.should == "(410) 555-1212"
19
+ end
20
+
21
+ it "should have 7 sections" do
22
+ @resume.should have(7).sections
23
+ end
24
+
25
+ it "should have sections in order" do
26
+ @resume.sections.map { |s| s.title }.should == [
27
+ "Career Goal",
28
+ "Qualifications Summary",
29
+ "Technical Skills",
30
+ "Professional Experience",
31
+ "Education",
32
+ "Specialized Training",
33
+ "Certification, Honors, and Professional Affiliations"
34
+ ]
35
+ end
36
+
37
+ it "should have a paragraph in the Career Goal section" do
38
+ @resume.sections[0].para.should_not be_blank
39
+ end
40
+
41
+ it "should have a paragraph in the Qualifications Summary section" do
42
+ @resume.sections[1].para.should_not be_blank
43
+ end
44
+
45
+ it "should have 4 items in the Technical Skills section" do
46
+ @resume.sections[2].should have(4).items
47
+ end
48
+
49
+ it "should have 3 periods in the Professional Experience section" do
50
+ @resume.sections[3].should have(3).periods
51
+ end
52
+
53
+ it "should have 4 items in the Systems Engineer period" do
54
+ @resume.sections[3].periods[0].should have(4).items
55
+ end
56
+
57
+ it "should have proper dates" do
58
+ @resume.sections[3].periods[0].dtstart.should == "1993"
59
+ @resume.sections[3].periods[0].dtend.should == "Present"
60
+ end
61
+ end
@@ -0,0 +1,37 @@
1
+ #--
2
+ # Copyright (c) 2009 Virgil Dimaguila
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person
5
+ # obtaining a copy of this software and associated documentation
6
+ # files (the "Software"), to deal in the Software without
7
+ # restriction, including without limitation the rights to use,
8
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the
10
+ # Software is furnished to do so, subject to the following
11
+ # conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ # OTHER DEALINGS IN THE SOFTWARE.
24
+ #++
25
+
26
+ require File.join(File.dirname(__FILE__), "spec_helper")
27
+
28
+ describe "Rendering a Resume to PDF" do
29
+ before do
30
+ @resume = ResumeTools::Resume.new
31
+ end
32
+
33
+ it "should render to PDF" do
34
+ result = @resume.render_pdf
35
+ result.should_not be_nil
36
+ end
37
+ end