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 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 'yaml'
6
- require 'io/console'
5
+ require 'martlet/cli'
7
6
 
8
- def usage
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)
@@ -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
@@ -6,7 +6,7 @@ module Martlet
6
6
 
7
7
  def initialize(email, password)
8
8
  @agent = Mechanize.new
9
- @email = format_email(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
- private
21
-
22
- def format_email(email)
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
@@ -1,3 +1,3 @@
1
1
  module Martlet
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
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
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: 2013-12-27 00:00:00.000000000 Z
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: 1.8.23
131
+ rubygems_version: 2.2.2
106
132
  signing_key:
107
- specification_version: 3
133
+ specification_version: 4
108
134
  summary: Client for McGill's student portal
109
135
  test_files: []