uw_learn 1.0.0.pre → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []