uw_learn 1.0.0.pre → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,28 @@
1
1
  require 'mechanize'
2
2
  require 'hpricot'
3
- require 'awesome_print'
4
3
  require 'open-uri'
5
4
  require 'httparty'
6
5
  require 'rainbow'
7
6
 
8
- require './lib/uw_learn/grade'
9
- require './lib/uw_learn/course'
7
+ require 'uw_learn/grade'
8
+ require 'uw_learn/course'
10
9
 
10
+ ##
11
+ # The UW Learn gem is a tiny web crawler for University of Waterloo students.
12
+ # It grabs their grades, and prints them out in the terminal.
13
+ #
14
+ # @author Gaurav Mali <hello@gauravmali.com>
15
+ # @version 1.0.1
16
+ #
11
17
  class Uwlearn
12
-
18
+
19
+ ##
20
+ # Creates a new instance of UW Learn account.
21
+ # Proceeds to login and scrape grades.
22
+ #
23
+ # @param [String] The student username
24
+ # @param [String] The student password
25
+ #
13
26
  def initialize(login, password)
14
27
  @user_login = login
15
28
  @user_passw = password
@@ -24,20 +37,38 @@ class Uwlearn
24
37
  start
25
38
  end
26
39
 
40
+ ##
41
+ # @!method login
42
+ # The username that the account was logged in with
43
+ # @return [String] The username
44
+ #
27
45
  def login
28
46
  @user_login
29
47
  end
30
48
 
49
+ ##
50
+ # @!method print_courses
51
+ # Prints out the names of all the courses
52
+ #
31
53
  def print_courses
32
54
  @learn_courses.each do |course|
33
55
  puts course.name.foreground(:yellow)
34
56
  end
35
57
  end
36
58
 
59
+ ##
60
+ # @!method courses
61
+ # The array of the user's courses in string format
62
+ # @return [Array] The user's courses
63
+ #
37
64
  def courses
38
65
  @learn_courses
39
66
  end
40
67
 
68
+ ##
69
+ # @!method print_grades
70
+ # Prints out every course's name and the grades registered with it
71
+ #
41
72
  def print_grades
42
73
  @learn_grades.each do |grade|
43
74
  puts grade.which_course?.foreground(:yellow) + ": ".foreground(:yellow)
@@ -45,43 +76,82 @@ class Uwlearn
45
76
  end
46
77
  end
47
78
 
79
+ ##
80
+ # @!method grades
81
+ # The array of user's grades in string format
82
+ # @return [Array] The user's grades
83
+ #
48
84
  def grades
49
85
  @learn_grades
50
86
  end
51
87
 
52
88
  private
53
89
 
90
+ ##
91
+ # learn_url
92
+ # Holding on to the learn URL for easy change. You may try simply changing this URL to a different University's URL.
93
+ # But remember, this is a hacky scrape job. Don't be surprised if things break.
94
+ #
54
95
  def learn_url
55
96
  "http://learn.uwaterloo.ca"
56
97
  end
57
98
 
99
+ ##
100
+ # course_url
101
+ # Just in case if D2L decides to change their markup, you know where to look for (hopefully) a quick fix.
102
+ #
58
103
  def course_url
59
104
  "/d2l/lp/ouHome/home.d2l?ou="
60
105
  end
61
106
 
107
+ ##
108
+ # course_html
109
+ # Just in case if D2L decides to change their markup, you know where to look for (hopefully) a quick fix.
110
+ #
62
111
  def course_html
63
- "//ul[@id='z_bl']//div[@class='dco_c']//a"
112
+ "//ul[@id='z_w']//div[@class='dco_c']//a"
113
+ end
114
+
115
+ ##
116
+ # error_html
117
+ # Just in case if D2L decides to change their markup, you know where to look for (hopefully) a quick fix.
118
+ #
119
+ def error_html
120
+ "//div[@class='error']"
64
121
  end
65
122
 
123
+ ##
124
+ # start
125
+ # The starting point of crawling. Calls the authentication and scraping methods.
126
+ #
66
127
  def start
128
+ # Very important object. Don't lose or overwrite it. Required for scraping.
67
129
  agent = Mechanize.new
68
130
 
69
131
  begin
132
+ # Authenticates in this method call.
70
133
  links = authenticate agent
134
+ # Scrapes in this method call.
71
135
  acquire links, agent
136
+
72
137
  puts "Grades acquired.".foreground(:cyan)
73
138
  rescue Exception => e
74
- puts e.message
75
- #puts e.backtrace.inspect
139
+ puts e.message
76
140
  rescue Mechanize::ResponseCodeError, Net::HTTPNotFound
77
141
  puts "Looks like the website has changed. Contact the developer to fix this issue.".foreground(:red)
78
142
  end
79
143
 
80
144
  end
81
145
 
146
+ ##
147
+ # authenticate
148
+ # Logs in using the user credentials.
149
+ # Returns an array of anchor(<a href=""></a>) elements of course links.
150
+ #
82
151
  def authenticate(agent)
83
152
  puts "Authenticating ".foreground(:cyan) + @user_login.foreground(:cyan) + "...".foreground(:cyan)
84
153
 
154
+ # Login form submission
85
155
  page = agent.get learn_url
86
156
  form = page.forms.first
87
157
  form.username = @user_login
@@ -89,13 +159,23 @@ class Uwlearn
89
159
  page = agent.submit form
90
160
 
91
161
  course_links = page.search course_html
92
- if course_links.empty?
162
+ login_error = page.search error_html
163
+
164
+ if login_error.empty? and course_links.empty?
165
+ raise "D2l has changed. Please contact the developer.".foreground(:red)
166
+ elsif !login_error.empty?
93
167
  raise "Couldn't authenticate. Please try again.".foreground(:red)
94
168
  end
95
169
 
96
170
  course_links
97
171
  end
98
172
 
173
+ ##
174
+ # acquire
175
+ # Extracts the course name and code for further scraping of grades.
176
+ # The scraping occurs in the initialization of the grade objects.
177
+ # Stores the grades and courses in two different arrays: @learn_courses and @learn_grades.
178
+ #
99
179
  def acquire(links, agent)
100
180
  puts "Acquiring data...".foreground(:cyan)
101
181
 
@@ -115,4 +195,4 @@ class Uwlearn
115
195
  end
116
196
  end
117
197
 
118
- end
198
+ end # Uwlearn
@@ -0,0 +1,40 @@
1
+ ##
2
+ # The course object represents a typical course a student would register for.
3
+ #
4
+ # @todo Add more featuers such as course news and dates
5
+ #
6
+ # @author Gaurav Mali <hello@gauravmali.com>
7
+ # @version 1.0.1
8
+ #
9
+
10
+ class Course
11
+ ##
12
+ # @!attribute [r] name
13
+ # @return [String] the course name
14
+ attr_reader :name
15
+
16
+ ##
17
+ # @!attribute [r] code
18
+ # @return [String] the course code
19
+ attr_reader :code
20
+
21
+ ##
22
+ # @!attribute [r] grade
23
+ # @return [Object] the grades of the particular course
24
+ attr_reader :grade
25
+
26
+ ##
27
+ # Creates a new instance of a course object.
28
+ # Simply holds on to string values and the grade object.
29
+ #
30
+ # @param [String] The course name
31
+ # @param [String] The course code
32
+ # @param [Object] The course grades
33
+ #
34
+ def initialize(name, code, grade)
35
+ @name = name
36
+ @code = code
37
+ @grade = grade
38
+ end
39
+
40
+ end # Course
@@ -0,0 +1,116 @@
1
+ require 'mechanize'
2
+ require 'hpricot'
3
+ require 'open-uri'
4
+
5
+ ##
6
+ # The grade object scrapes and holds on the grades of a particular course
7
+ #
8
+ # @todo Store the grades in a hash with key as a string and value as a number of some sort
9
+ #
10
+ # @author Gaurav Mali <hello@gauravmali.com>
11
+ # @version 1.0.1
12
+ #
13
+ class Grade
14
+
15
+ ##
16
+ # Creates a new instance of a course grade.
17
+ # Proceeds to scrape grades and store it in @grades.
18
+ #
19
+ # @param [String] The course name
20
+ # @param [String] The course code
21
+ # @param [Object] The Mechanize object for scraping
22
+ #
23
+ def initialize(name, code, agent)
24
+ @course_name = name
25
+ @course_code = code
26
+ @grades = Array.new
27
+
28
+ get_grades agent
29
+ end
30
+
31
+ ##
32
+ # @!method which_course?
33
+ # A way to find out which course a grade belongs to.
34
+ # @return [String] The course name
35
+ #
36
+ def which_course?
37
+ @course_name
38
+ end
39
+
40
+ ##
41
+ # @!method summary
42
+ # Returns a summary of grades for this particular course.
43
+ # If no grades were uploaded, user is notified so.
44
+ #
45
+ # @return [Array] The grades in string format.
46
+ #
47
+ def summary
48
+ if @grades.empty?
49
+ ("Sorry the instructor for " + @course_name + " has not uploaded any marks yet").foreground(:red)
50
+ else
51
+ @grades
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ ##
58
+ # grades_url
59
+ # Just in case if D2L decides to change their markup, you know where to look for (hopefully) a quick fix.
60
+ #
61
+ def grades_url
62
+ "https://learn.uwaterloo.ca/d2l/lms/grades/my_grades/main.d2l?ou=" + @course_code
63
+ end
64
+
65
+ ##
66
+ # grades?
67
+ # Are there any grades for this course?
68
+ # Returns true(yes) or false(no)
69
+ #
70
+ def grades?(report)
71
+ report.xpath("//td[@class='d_gempty']").to_s == ""
72
+ end
73
+
74
+ ##
75
+ # has_mark?
76
+ # Are there any marks in this table of grades?
77
+ # We don't want rows with no data in them.
78
+ #
79
+ # Returns true(yes) or false(no)
80
+ #
81
+ def has_mark?(row)
82
+ # Why didn't !(row/"label").blank? work?
83
+ (row/"label").to_s != ""
84
+ end
85
+
86
+ ##
87
+ # get_grades
88
+ # More scraping.
89
+ #
90
+ def get_grades(agent)
91
+ page = agent.get grades_url
92
+ report = page.parser
93
+
94
+ # Search for all rows that have both 'strong' and 'label' elements
95
+ if grades? report
96
+ rows = report.xpath("//table[@id='z_c']/tr")
97
+
98
+ if rows.empty?
99
+ raise "ERROR: Failed parsing at the rows. Please contact the developer.".foreground(:red)
100
+ end
101
+
102
+ rows.each do |row|
103
+ if has_mark? row
104
+ mark = (row/"label").last.inner_html.to_s
105
+ category = (row/"strong").inner_html.to_s
106
+
107
+ # Storing it as a string for now. Future releases will be different.
108
+ @grades << (category + " => " + mark)
109
+ end
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+
116
+ end # Grade
metadata CHANGED
@@ -1,17 +1,161 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uw_learn
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre
5
- prerelease: 6
4
+ version: 1.0.1
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Gaurav Mali
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-24 00:00:00.000000000 Z
13
- dependencies: []
14
- description: Gets student grades from University of Waterloo's D2L website.
12
+ date: 2012-09-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mechanize
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: hpricot
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: httparty
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rainbow
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: webmock
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: minitest
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: vcr
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: turn
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: rake
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ description: Displays student grades from D2l in the terminal.
15
159
  email: hello@gauravmali.com
16
160
  executables:
17
161
  - uw_learn
@@ -19,6 +163,8 @@ extensions: []
19
163
  extra_rdoc_files: []
20
164
  files:
21
165
  - lib/uw_learn.rb
166
+ - lib/uw_learn/grade.rb
167
+ - lib/uw_learn/course.rb
22
168
  - bin/uw_learn
23
169
  homepage: https://github.com/GMali/uw_learn
24
170
  licenses: []
@@ -26,6 +172,7 @@ post_install_message:
26
172
  rdoc_options: []
27
173
  require_paths:
28
174
  - lib
175
+ - lib
29
176
  required_ruby_version: !ruby/object:Gem::Requirement
30
177
  none: false
31
178
  requirements:
@@ -35,13 +182,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
35
182
  required_rubygems_version: !ruby/object:Gem::Requirement
36
183
  none: false
37
184
  requirements:
38
- - - ! '>'
185
+ - - ! '>='
39
186
  - !ruby/object:Gem::Version
40
- version: 1.3.1
187
+ version: '0'
41
188
  requirements: []
42
189
  rubyforge_project:
43
190
  rubygems_version: 1.8.24
44
191
  signing_key:
45
192
  specification_version: 3
46
- summary: Tiny web crawler for University of Waterloo students
193
+ summary: Tiny web crawler for University of Waterloo students.
47
194
  test_files: []