martlet 0.0.6 → 0.0.7
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 +4 -4
- data/.gitignore +1 -0
- data/bin/martlet +1 -2
- data/lib/martlet/calendar_exporter.rb +66 -0
- data/lib/martlet/calendar_helpers.rb +40 -0
- data/lib/martlet/cli.rb +11 -2
- data/lib/martlet/client.rb +10 -2
- data/lib/martlet/course.rb +7 -12
- data/lib/martlet/course_meeting.rb +14 -0
- data/lib/martlet/schedule.rb +2 -0
- data/lib/martlet/schedule_parser.rb +59 -17
- data/lib/martlet/version.rb +1 -1
- data/lib/martlet.rb +3 -0
- data/martlet.gemspec +2 -3
- metadata +11 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7a4692527f2677c94a47163661dc644e22bd2b0
|
4
|
+
data.tar.gz: 82efea83f3312f4910bafebebdbd0c6f99889b94
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e3c2707c80dc889363960d4669e26fbd8dafe72328061198795f76bbec8b34f98a898607b3b84538410df24fbc5d04af824315442409d7419bb16babe041156
|
7
|
+
data.tar.gz: af8da5fcb0c00dcb4da686fbe373a101b7ae899ae49a25684dc53b4646d34f7d7bdc16003a2d1802989fc9108739a94d11db9ad28b192f75884564402692ef5b
|
data/.gitignore
CHANGED
data/bin/martlet
CHANGED
@@ -0,0 +1,66 @@
|
|
1
|
+
module Martlet
|
2
|
+
class CalendarExporter
|
3
|
+
include CalendarHelpers
|
4
|
+
|
5
|
+
def initialize(filename, courses)
|
6
|
+
@filename = filename
|
7
|
+
@courses = courses
|
8
|
+
end
|
9
|
+
|
10
|
+
def export
|
11
|
+
f = File.new(@filename, 'w')
|
12
|
+
f.write("BEGIN:VCALENDAR\r\n")
|
13
|
+
|
14
|
+
@courses.each do |course|
|
15
|
+
course.meetings.each do |meeting|
|
16
|
+
if meeting.start_time
|
17
|
+
f.write(calendar_vevent(course, meeting))
|
18
|
+
else
|
19
|
+
puts "Warning: schedule information unavailable for #{course.number}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
f.write("END:VCALENDAR\r\n")
|
25
|
+
ensure
|
26
|
+
f.close
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def calendar_dtstart(meeting)
|
32
|
+
event_date = calendar_date(meeting.start_date)
|
33
|
+
start_time = calendar_time(meeting.start_time)
|
34
|
+
"#{event_date}T#{start_time}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def calendar_dtend(meeting)
|
38
|
+
event_date = calendar_date(meeting.start_date)
|
39
|
+
end_time = calendar_time(meeting.end_time)
|
40
|
+
"#{event_date}T#{end_time}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def calendar_rrule(meeting)
|
44
|
+
end_date = calendar_date(meeting.end_date)
|
45
|
+
repeat_days = calendar_days(meeting.days)
|
46
|
+
"FREQ=WEEKLY;INTERVAL=1;UNTIL=#{end_date}T235959;BYDAY=#{repeat_days}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def calendar_vevent(course, meeting)
|
50
|
+
begin_vevent = "BEGIN:VEVENT\r\n"
|
51
|
+
summary = "SUMMARY:#{course.number} - #{course.name}\r\n"
|
52
|
+
location = "LOCATION:#{meeting.location}\r\n"
|
53
|
+
dtstart = "DTSTART:#{calendar_dtstart(meeting)}\r\n"
|
54
|
+
dtend = "DTEND:#{calendar_dtend(meeting)}\r\n"
|
55
|
+
rrule = "RRULE:#{calendar_rrule(meeting)}\r\n"
|
56
|
+
end_vevent = "END:VEVENT\r\n"
|
57
|
+
|
58
|
+
if exclude_first_day?(meeting)
|
59
|
+
exdate = "EXDATE:#{calendar_dtstart(meeting)}\r\n"
|
60
|
+
[begin_vevent, summary, location, dtstart, dtend, rrule, exdate, end_vevent].join('')
|
61
|
+
else
|
62
|
+
[begin_vevent, summary, location, dtstart, dtend, rrule, end_vevent].join('')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Martlet
|
2
|
+
module CalendarHelpers
|
3
|
+
include DayConversions
|
4
|
+
|
5
|
+
def calendar_time(time)
|
6
|
+
sprintf "%02d%02d00", time.hour, time.min
|
7
|
+
end
|
8
|
+
|
9
|
+
def calendar_date(date)
|
10
|
+
sprintf "%04d%02d%02d", date.year, date.month, date.day
|
11
|
+
end
|
12
|
+
|
13
|
+
def calendar_day(day)
|
14
|
+
case day.capitalize
|
15
|
+
when 'Sunday' then 'SU'
|
16
|
+
when 'Monday' then 'MO'
|
17
|
+
when 'Tuesday' then 'TU'
|
18
|
+
when 'Wednesday' then 'WE'
|
19
|
+
when 'Thursday' then 'TH'
|
20
|
+
when 'Friday' then 'FR'
|
21
|
+
when 'Saturday' then 'SA'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def calendar_days(days)
|
26
|
+
repeat_days = days.map { |day| calendar_day(day) }
|
27
|
+
repeat_days.join(',')
|
28
|
+
end
|
29
|
+
|
30
|
+
def in_days?(date, days)
|
31
|
+
wday = date.wday
|
32
|
+
wdays = days.map { |day| number_from_day(day) }
|
33
|
+
wdays.include?(wday)
|
34
|
+
end
|
35
|
+
|
36
|
+
def exclude_first_day?(meeting)
|
37
|
+
!in_days?(meeting.start_date, meeting.days)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/martlet/cli.rb
CHANGED
@@ -7,7 +7,7 @@ module Martlet
|
|
7
7
|
default_task :grades
|
8
8
|
|
9
9
|
desc "grades", "Lists all your grades"
|
10
|
-
|
10
|
+
option :sort, aliases: 's', enum: ['course', 'grade']
|
11
11
|
def grades
|
12
12
|
puts 'Fetching grades...'
|
13
13
|
grades = client.grades
|
@@ -24,13 +24,16 @@ module Martlet
|
|
24
24
|
end
|
25
25
|
|
26
26
|
desc "courses SEMESTER YEAR", "List current courses or courses for given semester and year"
|
27
|
+
option :export, type: :boolean, default: false,
|
28
|
+
desc: "Export course schedule as an iCalendar (.ics) file in the current directory"
|
27
29
|
def courses(semester = nil, year = nil)
|
28
30
|
if semester.nil? || year.nil?
|
29
31
|
semester, year = current_semester_and_year
|
30
32
|
end
|
31
33
|
|
32
34
|
puts 'Fetching courses...'
|
33
|
-
|
35
|
+
schedule = client.schedule(semester, year)
|
36
|
+
courses = schedule.fetch_courses
|
34
37
|
course_name_size = courses.map { |c| c.name.length }.max
|
35
38
|
|
36
39
|
puts "#{semester.capitalize} #{year} courses"
|
@@ -41,6 +44,12 @@ module Martlet
|
|
41
44
|
end
|
42
45
|
|
43
46
|
puts 'No courses found' if courses.empty?
|
47
|
+
|
48
|
+
if options[:export] && !courses.empty?
|
49
|
+
filename = "#{semester}_#{year}.ics"
|
50
|
+
puts "Exporting courses to #{filename}..."
|
51
|
+
client.export_calendar(filename, courses)
|
52
|
+
end
|
44
53
|
end
|
45
54
|
|
46
55
|
private
|
data/lib/martlet/client.rb
CHANGED
@@ -17,9 +17,17 @@ module Martlet
|
|
17
17
|
transcript.fetch_grades
|
18
18
|
end
|
19
19
|
|
20
|
+
def schedule(semester, year)
|
21
|
+
Schedule.new(@agent, semester, year)
|
22
|
+
end
|
23
|
+
|
20
24
|
def courses(semester, year)
|
21
|
-
schedule
|
22
|
-
|
25
|
+
schedule(semester, year).fetch_courses
|
26
|
+
end
|
27
|
+
|
28
|
+
def export_calendar(filename, courses)
|
29
|
+
exporter = CalendarExporter.new(filename, courses)
|
30
|
+
exporter.export
|
23
31
|
end
|
24
32
|
end
|
25
33
|
end
|
data/lib/martlet/course.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
module Martlet
|
2
2
|
class Course
|
3
|
-
|
4
|
-
|
5
|
-
attr_accessor :name, :number, :term, :crn, :instructor, :credits, :level, :campus,
|
6
|
-
:time, :days, :location, :date_range, :type, :instructors
|
3
|
+
attr_accessor :name, :number, :term, :crn, :instructor, :credits, :level, :campus, :meetings
|
7
4
|
|
8
5
|
def initialize(args)
|
9
6
|
@name = args[:name] || ''
|
@@ -14,15 +11,13 @@ module Martlet
|
|
14
11
|
@credits = args[:credits]
|
15
12
|
@level = args[:level]
|
16
13
|
@campus = args[:campus]
|
17
|
-
@
|
18
|
-
@days = args[:days] || ''
|
19
|
-
@location = args[:location]
|
20
|
-
@date_range = args[:date_range]
|
21
|
-
@type = args[:type]
|
22
|
-
@instructors = args[:instructors]
|
14
|
+
@meetings = args[:meetings]
|
23
15
|
|
24
16
|
@name.gsub! /\.$/, ''
|
25
|
-
|
17
|
+
end
|
18
|
+
|
19
|
+
def location
|
20
|
+
meetings.first.location
|
26
21
|
end
|
27
22
|
end
|
28
|
-
end
|
23
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Martlet
|
2
|
+
class CourseMeeting
|
3
|
+
attr_reader :start_time, :end_time, :start_date, :end_date, :days, :location
|
4
|
+
|
5
|
+
def initialize(args)
|
6
|
+
@start_time = args[:start_time]
|
7
|
+
@end_time = args[:end_time]
|
8
|
+
@start_date = args[:start_date]
|
9
|
+
@end_date = args[:end_date]
|
10
|
+
@days = args[:days]
|
11
|
+
@location = args[:location]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/martlet/schedule.rb
CHANGED
@@ -1,25 +1,44 @@
|
|
1
1
|
module Martlet
|
2
2
|
class ScheduleParser
|
3
|
+
include DayConversions
|
4
|
+
|
3
5
|
def initialize(html)
|
4
6
|
@html = html
|
5
7
|
end
|
6
8
|
|
7
9
|
def parse_courses
|
8
|
-
courses = []
|
9
10
|
document = Nokogiri::HTML(@html)
|
10
11
|
course_tables = document.search("br+table[@class='datadisplaytable']")
|
11
|
-
course_times = document.search("br+table[@class='datadisplaytable']+table tr
|
12
|
+
course_times = document.search("br+table[@class='datadisplaytable']+table tr")
|
13
|
+
course_times = split_course_times(course_times)
|
12
14
|
|
13
|
-
course_tables.
|
15
|
+
courses = course_tables.map.with_index do |table, index|
|
14
16
|
course_name_info = table.search('caption').first
|
15
17
|
course_name_info = course_name_info.text.split(' - ')
|
16
18
|
course_name = course_name_info[0]
|
17
19
|
course_number = course_name_info[1]
|
18
20
|
|
19
|
-
course_time_info = course_times[index].search('td').map { |d| d.text.strip }
|
20
|
-
|
21
21
|
course_data = table.search('tr td').map { |d| d.text.strip }
|
22
|
-
|
22
|
+
course_time_info = course_times[index].map do |course_time|
|
23
|
+
course_time.search('td').map { |d| d.text.strip }
|
24
|
+
end
|
25
|
+
|
26
|
+
meetings = course_time_info.map do |course_time|
|
27
|
+
start_time, end_time = parse_time_range(course_time[0])
|
28
|
+
start_date, end_date = parse_date_range(course_time[3])
|
29
|
+
days = parse_days(course_time[1])
|
30
|
+
|
31
|
+
CourseMeeting.new({
|
32
|
+
start_time: start_time,
|
33
|
+
end_time: end_time,
|
34
|
+
start_date: start_date,
|
35
|
+
end_date: end_date,
|
36
|
+
days: days,
|
37
|
+
location: course_time[2]
|
38
|
+
})
|
39
|
+
end
|
40
|
+
|
41
|
+
Course.new({
|
23
42
|
name: course_name,
|
24
43
|
number: course_number,
|
25
44
|
term: course_data[0],
|
@@ -28,18 +47,41 @@ module Martlet
|
|
28
47
|
credits: course_data[5],
|
29
48
|
level: course_data[6],
|
30
49
|
campus: course_data[7],
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
50
|
+
meetings: meetings
|
51
|
+
})
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def split_course_times(course_times)
|
58
|
+
split = []
|
59
|
+
course_times.each do |info|
|
60
|
+
if headers?(info)
|
61
|
+
split << []
|
62
|
+
else
|
63
|
+
split.last << info
|
64
|
+
end
|
40
65
|
end
|
66
|
+
split
|
67
|
+
end
|
68
|
+
|
69
|
+
def headers?(row)
|
70
|
+
!row.search('th').empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
def parse_time_range(time_range)
|
74
|
+
times = time_range.scan(/\d+:\d+\s[AP]M/)
|
75
|
+
times.map { |time| Time.parse(time) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def parse_date_range(date_range)
|
79
|
+
dates = date_range.scan(/\w+\s\d+,\s\d+/)
|
80
|
+
dates.map { |date| Date.parse(date) }
|
81
|
+
end
|
41
82
|
|
42
|
-
|
83
|
+
def parse_days(days)
|
84
|
+
days.split('').map { |letter| day_from_letter(letter) }
|
43
85
|
end
|
44
86
|
end
|
45
|
-
end
|
87
|
+
end
|
data/lib/martlet/version.rb
CHANGED
data/lib/martlet.rb
CHANGED
@@ -5,9 +5,12 @@ require 'martlet/transcript'
|
|
5
5
|
require 'martlet/transcript_parser'
|
6
6
|
require 'martlet/schedule'
|
7
7
|
require 'martlet/schedule_parser'
|
8
|
+
require 'martlet/calendar_helpers'
|
9
|
+
require 'martlet/calendar_exporter'
|
8
10
|
require 'martlet/record'
|
9
11
|
require 'martlet/grade'
|
10
12
|
require 'martlet/course'
|
13
|
+
require 'martlet/course_meeting'
|
11
14
|
require 'martlet/version'
|
12
15
|
|
13
16
|
module Martlet
|
data/martlet.gemspec
CHANGED
@@ -19,9 +19,8 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
-
spec.add_development_dependency "rake"
|
23
|
-
spec.add_development_dependency "pry"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.3"
|
24
23
|
|
25
24
|
spec.add_dependency "mechanize", "~> 2.7"
|
26
|
-
spec.add_dependency "thor", "~> 0.19
|
25
|
+
spec.add_dependency "thor", "~> 0.19"
|
27
26
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: martlet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Coco
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -28,30 +28,16 @@ dependencies:
|
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
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
|
-
- - ">="
|
31
|
+
- - "~>"
|
46
32
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
33
|
+
version: '10.3'
|
48
34
|
type: :development
|
49
35
|
prerelease: false
|
50
36
|
version_requirements: !ruby/object:Gem::Requirement
|
51
37
|
requirements:
|
52
|
-
- - "
|
38
|
+
- - "~>"
|
53
39
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
40
|
+
version: '10.3'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: mechanize
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,14 +58,14 @@ dependencies:
|
|
72
58
|
requirements:
|
73
59
|
- - "~>"
|
74
60
|
- !ruby/object:Gem::Version
|
75
|
-
version: 0.19
|
61
|
+
version: '0.19'
|
76
62
|
type: :runtime
|
77
63
|
prerelease: false
|
78
64
|
version_requirements: !ruby/object:Gem::Requirement
|
79
65
|
requirements:
|
80
66
|
- - "~>"
|
81
67
|
- !ruby/object:Gem::Version
|
82
|
-
version: 0.19
|
68
|
+
version: '0.19'
|
83
69
|
description: Ruby client for McGill's student portal, Minerva.
|
84
70
|
email:
|
85
71
|
- hello@alexcoco.com
|
@@ -96,9 +82,12 @@ files:
|
|
96
82
|
- bin/martlet
|
97
83
|
- lib/martlet.rb
|
98
84
|
- lib/martlet/authenticator.rb
|
85
|
+
- lib/martlet/calendar_exporter.rb
|
86
|
+
- lib/martlet/calendar_helpers.rb
|
99
87
|
- lib/martlet/cli.rb
|
100
88
|
- lib/martlet/client.rb
|
101
89
|
- lib/martlet/course.rb
|
90
|
+
- lib/martlet/course_meeting.rb
|
102
91
|
- lib/martlet/day_conversions.rb
|
103
92
|
- lib/martlet/grade.rb
|
104
93
|
- lib/martlet/record.rb
|