martlet 0.0.5 → 0.0.6
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.
- checksums.yaml +7 -0
- data/bin/martlet +2 -57
- data/lib/martlet/cli.rb +91 -0
- data/lib/martlet/client.rb +6 -11
- data/lib/martlet/course.rb +28 -0
- data/lib/martlet/day_conversions.rb +35 -0
- data/lib/martlet/grade.rb +21 -0
- data/lib/martlet/schedule.rb +38 -0
- data/lib/martlet/schedule_parser.rb +45 -0
- data/lib/martlet/version.rb +1 -1
- data/lib/martlet.rb +5 -0
- data/martlet.gemspec +2 -0
- metadata +48 -22
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 35a2b85f2c458fe8574f563eda6789bafe0944c3
|
4
|
+
data.tar.gz: 56f1b0405e9918adc07b75a57523d9fc825c0856
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 568ec7e3a3347e0273d48e03158d0906217eacf942bd46dc2cd6e4d6ec318b285f3097d7a65112cb07143a75ed49a471d1330e14e5a1a0fd99aab9f5ee28a59b
|
7
|
+
data.tar.gz: c3ac26d611c43935969620f116344c7ed0112fa4c7381bb7a6d5c0a312170b608c900d4d097ab8ed6d9a8d555e5033e8202ae2607cd5f717837fd885affbd309
|
data/bin/martlet
CHANGED
@@ -2,61 +2,6 @@
|
|
2
2
|
$:.unshift File.join File.dirname(__FILE__), '..', 'lib'
|
3
3
|
require 'rubygems'
|
4
4
|
require 'martlet'
|
5
|
-
require '
|
6
|
-
require 'io/console'
|
5
|
+
require 'martlet/cli'
|
7
6
|
|
8
|
-
|
9
|
-
puts <<-STR
|
10
|
-
USAGE:
|
11
|
-
martlet [command]
|
12
|
-
|
13
|
-
COMMANDS:
|
14
|
-
grades Lists all your grades. This is the default command.
|
15
|
-
STR
|
16
|
-
end
|
17
|
-
|
18
|
-
def get_credentials
|
19
|
-
config_path = File.join ENV['HOME'], '.martlet'
|
20
|
-
email = nil
|
21
|
-
password = nil
|
22
|
-
|
23
|
-
if File.exist?(config_path)
|
24
|
-
config = YAML.load_file config_path
|
25
|
-
email = config['email']
|
26
|
-
password = config['password']
|
27
|
-
else
|
28
|
-
print 'Minerva email: '
|
29
|
-
email = gets.chomp
|
30
|
-
|
31
|
-
print 'Password: '
|
32
|
-
password = STDIN.noecho(&:gets).chomp
|
33
|
-
puts
|
34
|
-
end
|
35
|
-
|
36
|
-
return email, password
|
37
|
-
end
|
38
|
-
|
39
|
-
def run(args)
|
40
|
-
case args.shift
|
41
|
-
when nil, 'grades'
|
42
|
-
email, password = get_credentials
|
43
|
-
|
44
|
-
puts 'Authenticating...'
|
45
|
-
client = Martlet.new(email, password)
|
46
|
-
|
47
|
-
puts 'Fetching grades...'
|
48
|
-
grades = client.grades
|
49
|
-
|
50
|
-
grades.each do |number, grade|
|
51
|
-
puts "#{number}: #{grade}"
|
52
|
-
end
|
53
|
-
else
|
54
|
-
usage
|
55
|
-
end
|
56
|
-
rescue Martlet::AuthenticationError => e
|
57
|
-
puts "An error has occurred: #{e.message}"
|
58
|
-
end
|
59
|
-
|
60
|
-
args = ARGV.dup
|
61
|
-
ARGV.clear
|
62
|
-
run(args)
|
7
|
+
Martlet::CLI.start(ARGV)
|
data/lib/martlet/cli.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'yaml'
|
3
|
+
require 'io/console'
|
4
|
+
|
5
|
+
module Martlet
|
6
|
+
class CLI < Thor
|
7
|
+
default_task :grades
|
8
|
+
|
9
|
+
desc "grades", "Lists all your grades"
|
10
|
+
method_option :sort, aliases: 's', enum: ['course', 'grade']
|
11
|
+
def grades
|
12
|
+
puts 'Fetching grades...'
|
13
|
+
grades = client.grades
|
14
|
+
|
15
|
+
grades = case options['sort']
|
16
|
+
when 'course' then grades.sort_by { |k,v| k }
|
17
|
+
when 'grade' then grades.sort_by { |k,v| Grade.new v }
|
18
|
+
else grades
|
19
|
+
end
|
20
|
+
|
21
|
+
grades.each do |number, grade|
|
22
|
+
puts "#{number}: #{grade}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "courses SEMESTER YEAR", "List current courses or courses for given semester and year"
|
27
|
+
def courses(semester = nil, year = nil)
|
28
|
+
if semester.nil? || year.nil?
|
29
|
+
semester, year = current_semester_and_year
|
30
|
+
end
|
31
|
+
|
32
|
+
puts 'Fetching courses...'
|
33
|
+
courses = client.courses(semester, year)
|
34
|
+
course_name_size = courses.map { |c| c.name.length }.max
|
35
|
+
|
36
|
+
puts "#{semester.capitalize} #{year} courses"
|
37
|
+
|
38
|
+
courses.each do |course|
|
39
|
+
course_row_format = "%-11s %-#{course_name_size}s @ %s\n"
|
40
|
+
printf course_row_format, "#{course.number}", course.name, course.location
|
41
|
+
end
|
42
|
+
|
43
|
+
puts 'No courses found' if courses.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def credentials
|
49
|
+
email = nil
|
50
|
+
password = nil
|
51
|
+
|
52
|
+
if File.exist?(config_path)
|
53
|
+
# Get credentials from config file
|
54
|
+
config = YAML.load_file config_path
|
55
|
+
email = config['email']
|
56
|
+
password = config['password']
|
57
|
+
else
|
58
|
+
# Get credentials from stdin
|
59
|
+
print 'Minerva email: '
|
60
|
+
email = gets.chomp
|
61
|
+
|
62
|
+
print 'Password: '
|
63
|
+
password = STDIN.noecho(&:gets).chomp
|
64
|
+
puts
|
65
|
+
end
|
66
|
+
|
67
|
+
return email, password
|
68
|
+
end
|
69
|
+
|
70
|
+
def client
|
71
|
+
@client ||= begin
|
72
|
+
puts 'Authenticating...'
|
73
|
+
Martlet.new(*credentials)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def config_path
|
78
|
+
"#{ENV['HOME']}/.martlet"
|
79
|
+
end
|
80
|
+
|
81
|
+
def current_semester_and_year
|
82
|
+
semester = case Time.now.month
|
83
|
+
when 1..4 then 'winter'
|
84
|
+
when 5..8 then 'summer'
|
85
|
+
when 9..12 then 'fall'
|
86
|
+
end
|
87
|
+
|
88
|
+
return semester, Time.now.year
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/martlet/client.rb
CHANGED
@@ -6,7 +6,7 @@ module Martlet
|
|
6
6
|
|
7
7
|
def initialize(email, password)
|
8
8
|
@agent = Mechanize.new
|
9
|
-
@email =
|
9
|
+
@email = email.include?('@') ? email : "#{email}@mail.mcgill.ca"
|
10
10
|
|
11
11
|
authenticator = Authenticator.new(@agent)
|
12
12
|
authenticator.authenticate(@email, password)
|
@@ -16,15 +16,10 @@ module Martlet
|
|
16
16
|
transcript = Transcript.new(@agent)
|
17
17
|
transcript.fetch_grades
|
18
18
|
end
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
if email.include?('@')
|
24
|
-
email
|
25
|
-
else
|
26
|
-
"#{email}@mail.mcgill.ca"
|
27
|
-
end
|
19
|
+
|
20
|
+
def courses(semester, year)
|
21
|
+
schedule = Schedule.new(@agent, semester, year)
|
22
|
+
schedule.fetch_courses
|
28
23
|
end
|
29
24
|
end
|
30
|
-
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Martlet
|
2
|
+
class Course
|
3
|
+
include DayConversions
|
4
|
+
|
5
|
+
attr_accessor :name, :number, :term, :crn, :instructor, :credits, :level, :campus,
|
6
|
+
:time, :days, :location, :date_range, :type, :instructors
|
7
|
+
|
8
|
+
def initialize(args)
|
9
|
+
@name = args[:name] || ''
|
10
|
+
@number = args[:number]
|
11
|
+
@term = args[:term]
|
12
|
+
@crn = args[:crn]
|
13
|
+
@instructor = args[:instructor]
|
14
|
+
@credits = args[:credits]
|
15
|
+
@level = args[:level]
|
16
|
+
@campus = args[:campus]
|
17
|
+
@time = args[:time]
|
18
|
+
@days = args[:days] || ''
|
19
|
+
@location = args[:location]
|
20
|
+
@date_range = args[:date_range]
|
21
|
+
@type = args[:type]
|
22
|
+
@instructors = args[:instructors]
|
23
|
+
|
24
|
+
@name.gsub! /\.$/, ''
|
25
|
+
@days = @days.split('').map { |letter| day_from_letter(letter) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Martlet
|
2
|
+
module DayConversions
|
3
|
+
def day_from_letter(letter)
|
4
|
+
case letter.upcase
|
5
|
+
when 'M' then 'Monday'
|
6
|
+
when 'T' then 'Tuesday'
|
7
|
+
when 'W' then 'Wednesday'
|
8
|
+
when 'R' then 'Thursday'
|
9
|
+
when 'F' then 'Friday'
|
10
|
+
else letter
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def short_day_name(day)
|
15
|
+
case day.capitalize
|
16
|
+
when 'M', 'Monday' then 'Mon'
|
17
|
+
when 'T', 'Tuesday' then 'Tue'
|
18
|
+
when 'W', 'Wednesday' then 'Wed'
|
19
|
+
when 'R', 'Thursday' then 'Thu'
|
20
|
+
when 'F', 'Friday' then 'Fri'
|
21
|
+
else day
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def number_from_day(day)
|
26
|
+
case day.capitalize
|
27
|
+
when 'M', 'Monday' then 1
|
28
|
+
when 'T', 'Tuesday' then 2
|
29
|
+
when 'W', 'Wednesday' then 3
|
30
|
+
when 'R', 'Thursday' then 4
|
31
|
+
when 'F', 'Friday' then 5
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Martlet
|
2
|
+
class Grade
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
attr_reader :letter
|
6
|
+
|
7
|
+
def initialize(letter)
|
8
|
+
@letter = letter.upcase
|
9
|
+
end
|
10
|
+
|
11
|
+
def <=>(other)
|
12
|
+
letters.index(letter) <=> letters.index(other.letter)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def letters
|
18
|
+
%w{P S R CO A A- B+ B B- C+ C D F U HH J K KE K* KF IC KK L LE L* NA && NE NR W WF WL W-- -- IP Q}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Martlet
|
2
|
+
class Schedule
|
3
|
+
def initialize(agent, semester, year)
|
4
|
+
@agent = agent
|
5
|
+
@semester = semester
|
6
|
+
@year = year
|
7
|
+
@html = fetch_schedule_html
|
8
|
+
end
|
9
|
+
|
10
|
+
def fetch_courses
|
11
|
+
parser = ScheduleParser.new(@html)
|
12
|
+
parser.parse_courses
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def schedule_url
|
18
|
+
'https://horizon.mcgill.ca/pban1/bwskfshd.P_CrseSchdDetl'
|
19
|
+
end
|
20
|
+
|
21
|
+
def number_from_semester(semester)
|
22
|
+
case semester.to_s.downcase
|
23
|
+
when 'winter' then '01'
|
24
|
+
when 'summer' then '05'
|
25
|
+
when 'fall' then '09'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def schedule_params
|
30
|
+
{ 'term_in' => "#{@year}#{number_from_semester(@semester)}" }
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch_schedule_html
|
34
|
+
page = @agent.post(schedule_url, schedule_params)
|
35
|
+
page.body
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Martlet
|
2
|
+
class ScheduleParser
|
3
|
+
def initialize(html)
|
4
|
+
@html = html
|
5
|
+
end
|
6
|
+
|
7
|
+
def parse_courses
|
8
|
+
courses = []
|
9
|
+
document = Nokogiri::HTML(@html)
|
10
|
+
course_tables = document.search("br+table[@class='datadisplaytable']")
|
11
|
+
course_times = document.search("br+table[@class='datadisplaytable']+table tr+tr")
|
12
|
+
|
13
|
+
course_tables.each_with_index do |table, index|
|
14
|
+
course_name_info = table.search('caption').first
|
15
|
+
course_name_info = course_name_info.text.split(' - ')
|
16
|
+
course_name = course_name_info[0]
|
17
|
+
course_number = course_name_info[1]
|
18
|
+
|
19
|
+
course_time_info = course_times[index].search('td').map { |d| d.text.strip }
|
20
|
+
|
21
|
+
course_data = table.search('tr td').map { |d| d.text.strip }
|
22
|
+
args = {
|
23
|
+
name: course_name,
|
24
|
+
number: course_number,
|
25
|
+
term: course_data[0],
|
26
|
+
crn: course_data[1],
|
27
|
+
instructor: course_data[3],
|
28
|
+
credits: course_data[5],
|
29
|
+
level: course_data[6],
|
30
|
+
campus: course_data[7],
|
31
|
+
time: course_time_info[0],
|
32
|
+
days: course_time_info[1],
|
33
|
+
location: course_time_info[2],
|
34
|
+
date_range: course_time_info[3],
|
35
|
+
type: course_time_info[4],
|
36
|
+
instructors: course_time_info[5]
|
37
|
+
}
|
38
|
+
|
39
|
+
courses << Course.new(args)
|
40
|
+
end
|
41
|
+
|
42
|
+
courses
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/martlet/version.rb
CHANGED
data/lib/martlet.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
require 'martlet/client'
|
2
2
|
require 'martlet/authenticator'
|
3
|
+
require 'martlet/day_conversions'
|
3
4
|
require 'martlet/transcript'
|
4
5
|
require 'martlet/transcript_parser'
|
6
|
+
require 'martlet/schedule'
|
7
|
+
require 'martlet/schedule_parser'
|
5
8
|
require 'martlet/record'
|
9
|
+
require 'martlet/grade'
|
10
|
+
require 'martlet/course'
|
6
11
|
require 'martlet/version'
|
7
12
|
|
8
13
|
module Martlet
|
data/martlet.gemspec
CHANGED
@@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
22
|
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "pry"
|
23
24
|
|
24
25
|
spec.add_dependency "mechanize", "~> 2.7"
|
26
|
+
spec.add_dependency "thor", "~> 0.19.1"
|
25
27
|
end
|
metadata
CHANGED
@@ -1,64 +1,85 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: martlet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.6
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Alex Coco
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-04-18 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: bundler
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- - ~>
|
17
|
+
- - "~>"
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '1.3'
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- - ~>
|
24
|
+
- - "~>"
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '1.3'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: rake
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - ">="
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: '0'
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
44
53
|
- !ruby/object:Gem::Version
|
45
54
|
version: '0'
|
46
55
|
- !ruby/object:Gem::Dependency
|
47
56
|
name: mechanize
|
48
57
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
58
|
requirements:
|
51
|
-
- - ~>
|
59
|
+
- - "~>"
|
52
60
|
- !ruby/object:Gem::Version
|
53
61
|
version: '2.7'
|
54
62
|
type: :runtime
|
55
63
|
prerelease: false
|
56
64
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
65
|
requirements:
|
59
|
-
- - ~>
|
66
|
+
- - "~>"
|
60
67
|
- !ruby/object:Gem::Version
|
61
68
|
version: '2.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: thor
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.19.1
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.19.1
|
62
83
|
description: Ruby client for McGill's student portal, Minerva.
|
63
84
|
email:
|
64
85
|
- hello@alexcoco.com
|
@@ -67,7 +88,7 @@ executables:
|
|
67
88
|
extensions: []
|
68
89
|
extra_rdoc_files: []
|
69
90
|
files:
|
70
|
-
- .gitignore
|
91
|
+
- ".gitignore"
|
71
92
|
- Gemfile
|
72
93
|
- LICENSE.txt
|
73
94
|
- README.md
|
@@ -75,8 +96,14 @@ files:
|
|
75
96
|
- bin/martlet
|
76
97
|
- lib/martlet.rb
|
77
98
|
- lib/martlet/authenticator.rb
|
99
|
+
- lib/martlet/cli.rb
|
78
100
|
- lib/martlet/client.rb
|
101
|
+
- lib/martlet/course.rb
|
102
|
+
- lib/martlet/day_conversions.rb
|
103
|
+
- lib/martlet/grade.rb
|
79
104
|
- lib/martlet/record.rb
|
105
|
+
- lib/martlet/schedule.rb
|
106
|
+
- lib/martlet/schedule_parser.rb
|
80
107
|
- lib/martlet/transcript.rb
|
81
108
|
- lib/martlet/transcript_parser.rb
|
82
109
|
- lib/martlet/version.rb
|
@@ -84,26 +111,25 @@ files:
|
|
84
111
|
homepage: https://github.com/alexcoco/martlet
|
85
112
|
licenses:
|
86
113
|
- MIT
|
114
|
+
metadata: {}
|
87
115
|
post_install_message:
|
88
116
|
rdoc_options: []
|
89
117
|
require_paths:
|
90
118
|
- lib
|
91
119
|
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
-
none: false
|
93
120
|
requirements:
|
94
|
-
- -
|
121
|
+
- - ">="
|
95
122
|
- !ruby/object:Gem::Version
|
96
123
|
version: '0'
|
97
124
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
-
none: false
|
99
125
|
requirements:
|
100
|
-
- -
|
126
|
+
- - ">="
|
101
127
|
- !ruby/object:Gem::Version
|
102
128
|
version: '0'
|
103
129
|
requirements: []
|
104
130
|
rubyforge_project:
|
105
|
-
rubygems_version:
|
131
|
+
rubygems_version: 2.2.2
|
106
132
|
signing_key:
|
107
|
-
specification_version:
|
133
|
+
specification_version: 4
|
108
134
|
summary: Client for McGill's student portal
|
109
135
|
test_files: []
|