Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
SRCT
schedules
Commits
ec853376
Commit
ec853376
authored
Jun 10, 2018
by
Zac Wood
Browse files
Started adding docs, cleaned up, ensured tests pass
parent
91a2305f
Changes
9
Hide whitespace changes
Inline
Side-by-side
schedules_api/app/controllers/calendar_generator_controller.rb
View file @
ec853376
...
...
@@ -4,18 +4,20 @@ require 'time'
class
CalendarGeneratorController
<
ApplicationController
def
generate
cal
=
Icalendar
::
Calendar
.
new
params
[
:_json
].
each
do
|
crn
|
params
[
:_json
].
each
do
|
crn
|
# for each CRN sent by the post request
section
=
Section
.
find_by_crn
(
crn
)
event
=
generate_event_from_section
(
section
)
cal
.
add_event
(
event
)
end
render
plain:
cal
.
to_ical
render
plain:
cal
.
to_ical
# render a plaintext iCal file
end
private
# Configures a calendar event from a given section
# @param section [Section]
def
generate_event_from_section
(
section
)
event
=
Icalendar
::
Event
.
new
...
...
@@ -30,6 +32,10 @@ class CalendarGeneratorController < ApplicationController
event
end
# Format a DateTime string based on a given date and time
# @param date [String]
# @param time [String]
# @return [String]
def
formatted_datetime_str
(
date
,
time
)
formatted_date
=
date
.
to_s
.
tr
(
'-'
,
''
)
formatted_time
=
Time
.
parse
(
time
).
strftime
(
"%H%M%S"
)
...
...
@@ -37,6 +43,7 @@ class CalendarGeneratorController < ApplicationController
"
#{
formatted_date
}
T
#{
formatted_time
}
"
end
# Mapping of days as represented by GMU to the iCal standard
DAYS
=
{
"M"
=>
"MO"
,
"T"
=>
"TU"
,
...
...
@@ -47,6 +54,10 @@ class CalendarGeneratorController < ApplicationController
"U"
=>
"SU"
}.
freeze
# Generates a recurrence rule string descripting which day the class event
# should take place on
# @param section [Section]
# @return [String]
def
recurrence_rule_str
(
section
)
days
=
section
.
days
.
split
(
""
).
map
do
|
day
|
DAYS
[
day
]
...
...
@@ -55,8 +66,12 @@ class CalendarGeneratorController < ApplicationController
"FREQ=WEEKLY;UNTIL=
#{
formatted_datetime_str
(
section
.
end_date
,
section
.
end_time
)
}
;BYDAY=
#{
days
.
join
(
','
)
}
"
end
# Get all dates that should excluded from the schedule
# @param section [Section]
# @return [Array]
def
exdates_for_section
(
section
)
exdates
=
Closure
.
where
(
semester:
section
.
course
.
semester
).
map
{
|
closure
|
# Generate exdates for all closures in a semester
exdates
=
Closure
.
where
(
semester:
section
.
course
.
semester
).
map
{
|
closure
|
generate_exdate
(
closure
.
date
.
to_formatted_s
(
:number
),
section
.
start_time
)
}
...
...
@@ -72,7 +87,12 @@ class CalendarGeneratorController < ApplicationController
exdates
end
# Generate a DataTime to use as an exdate
# @param date [String]
# @param time [String]
# @return [Icalendar::Values::DateTime]
def
generate_exdate
(
date
,
time
)
# format the time for use in a DateTime
formatted_time
=
Time
.
parse
(
time
).
strftime
(
"%H%M%S"
)
Icalendar
::
Values
::
DateTime
.
new
(
"
#{
date
}
T
#{
formatted_time
}
"
)
end
...
...
schedules_api/app/controllers/home_controller.rb
deleted
100644 → 0
View file @
91a2305f
# Defines actions for the homepage
class
HomeController
<
ApplicationController
def
index
;
end
end
schedules_api/app/models/course.rb
View file @
ec853376
...
...
@@ -11,6 +11,7 @@ class Course < ApplicationRecord
validates
:semester_id
,
presence:
true
# Returns all +Section+ objects that belong to this course.
# @return [Array]
def
sections
Section
.
where
course_id:
id
end
...
...
schedules_api/app/models/section.rb
View file @
ec853376
...
...
@@ -8,11 +8,6 @@ class Section < ApplicationRecord
# Ensure all necessary fields are present.
validates
:name
,
presence:
true
validates
:crn
,
presence:
true
# Unsure if necessary
# validates :section_type, presence: true
validates
:title
,
presence:
true
# validates :start_date, presence: true
# validates :end_date, presence: true
# validates :days, presence: true
validates
:course_id
,
presence:
true
end
schedules_api/db/excel_loader.rb
View file @
ec853376
# This file is no longer being used.
# Data is now being parsed from Patriot Web.
require
'rubyXL'
# Provides utilities for loading schedules from GMU's excel files.
...
...
schedules_api/db/patriot_web_networker.rb
View file @
ec853376
require
'httparty'
module
PatriotWeb
# Contains utilities for making HTTP requests to PatriotWeb
class
Networker
def
fetch_page_containing_semester_data
HTTParty
.
get
(
'https://patriotweb.gmu.edu/pls/prod/bwckschd.p_disp_dyn_sched'
)
...
...
schedules_api/db/patriot_web_parser.rb
View file @
ec853376
...
...
@@ -2,61 +2,75 @@ require_relative 'patriot_web_networker'
require
'nokogiri'
class
String
# Checks if a String is a alphanumeric
def
alpha?
!!
match
(
/^[[:alpha:]]+$/
)
end
end
module
PatriotWeb
# Contains methods for parsing data retrieved from Patriot Web
class
Parser
def
initialize
@networker
=
PatriotWeb
::
Networker
.
new
end
# Parses all semesters avaliable on Patriot Web
def
parse_semesters
response
=
@networker
.
fetch_page_containing_semester_data
searcher
=
Nokogiri
::
HTML
(
response
)
response
=
@networker
.
fetch_page_containing_semester_data
document
=
Nokogiri
::
HTML
(
response
)
# parse the document from the HTTP response
get_semesters_from_option_values
(
searcher
).
compact
get_semesters_from_option_values
(
document
).
compact
end
# Parses subjects belonging to a given semester id
# @param semester_id [Integer]
def
parse_subjects
(
semester_id
)
response
=
@networker
.
fetch_subjects
(
semester_id
)
searcher
=
Nokogiri
::
HTML
(
response
)
get_alpha_option_values
(
searcher
)
document
=
Nokogiri
::
HTML
(
response
)
get_subject_codes_from_option_values
(
document
)
end
# Parses all courses belonging to a given subject
# @param subject [String]
def
parse_courses_in_subject
(
subject
)
resp
=
@networker
.
fetch_courses_in_subject
(
subject
)
searcher
=
Nokogiri
::
HTML
(
resp
)
feed_course_info
(
searcher
)
resp
onse
=
@networker
.
fetch_courses_in_subject
(
subject
)
document
=
Nokogiri
::
HTML
(
resp
onse
)
feed_course_info
(
document
)
end
private
def
get_alpha_option_values
(
searcher
)
searcher
.
xpath
(
'//*[@id="subj_id"]/option'
).
map
do
|
opt
|
if
opt
.
attr
(
'value'
).
strip
.
alpha?
opt
.
attr
(
'value'
)
# Parse the values of all different options on the Patriot Web
# semester select page
# @param document [Nokogiri::HTML::Document]
def
get_semesters_from_option_values
(
document
)
document
.
css
(
'option'
).
map
do
|
opt
|
# for each option value
if
opt
.
attr
(
'value'
).
start_with?
'20'
# ensure it is a semester value
opt
.
attr
(
'value'
)
# return the value
end
end
end
def
get_semesters_from_option_values
(
searcher
)
searcher
.
css
(
'option'
).
map
do
|
opt
|
if
opt
.
attr
(
'value'
).
start_with?
'20'
opt
.
attr
(
'value'
)
# Parse all subject codes from the select element on the Patriot Web
# subject select page
# @param document [Nokogiri::HTML::Document]
def
get_subject_codes_from_option_values
(
document
)
document
.
xpath
(
'//*[@id="subj_id"]/option'
).
map
do
|
opt
|
# for each option value under "subj_id"
if
opt
.
attr
(
'value'
).
strip
.
alpha?
# if the value is alphanumeric
opt
.
attr
(
'value'
)
# return the value
end
end
end
# TODO write docs
def
feed_course_info
(
searcher
)
table
=
searcher
.
css
(
'html body div.pagebodydiv table.datadisplaytable'
)
# find the table containing the courses
table
=
searcher
.
css
(
'html body div.pagebodydiv table.datadisplaytable'
)
data
=
{}
currentobj
=
nil
table
.
css
(
'table.datadisplaytable'
).
first
.
children
.
each
do
|
row
|
next
unless
row
.
name
==
'tr'
table
.
css
(
'table.datadisplaytable'
).
first
.
children
.
each
do
|
row
|
# for each row in the table
next
unless
row
.
name
==
'tr'
# only search table rows, ignore headers
row
.
children
.
each
do
|
item
|
currentobj
=
sort_item
(
item
,
currentobj
,
data
)
end
...
...
@@ -64,6 +78,7 @@ module PatriotWeb
data
end
# TODO break this up and write docs
def
sort_item
(
item
,
currentobj
,
data
)
if
item
.
name
==
'th'
if
item
.
to_html
.
include?
'-'
...
...
@@ -104,7 +119,8 @@ module PatriotWeb
end
currentobj
end
# TODO break this up and write docs
def
get_details
(
data
,
titledetails
,
titledata
)
crn
=
titledetails
[
1
].
strip
data
[
crn
]
=
{}
unless
data
[
titledetails
[
1
]]
...
...
schedules_api/db/seeds.rb
View file @
ec853376
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
#
# Examples:
#
# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
# Character.create(name: 'Luke', movie: movies.first)
require_relative
'patriot_web_parser'
require
'thwait'
...
...
@@ -16,7 +11,10 @@ threads = []
total
=
[]
parser
=
PatriotWeb
::
Parser
.
new
# get the first semester only -- no need to ddos patriot web
semester
=
parser
.
parse_semesters
.
first
# parse all subjects and their courses in the semester
parser
.
parse_subjects
(
semester
).
each
do
|
subject
|
threads
<<
Thread
.
new
{
total
<<
parser
.
parse_courses_in_subject
(
subject
)
...
...
@@ -27,20 +25,25 @@ end
# subject = parser.parse_subjects(semester).first
# total << parser.parse_courses_in_subject(subject)
# wait for all the threads to finish
ThreadsWait
.
all_waits
(
*
threads
)
# delete everything in the current database
Closure
.
delete_all
Section
.
delete_all
Course
.
delete_all
Semester
.
delete_all
# create a semester for the next semester
semester
=
Semester
.
create!
season:
'Fall'
,
year:
2018
semester
.
save!
total
.
each
do
|
subject
|
subject
.
each_value
do
|
section
|
total
.
each
do
|
subject
|
# for each course
subject
.
each_value
do
|
section
|
# for each value in the subject hash
# ensure all necessary fields are present
next
unless
(
section
.
key?
"date_range"
)
&&
(
section
.
key?
"instructors"
)
&&
(
section
.
key?
"days"
)
# create a course and set its semester
course
=
Course
.
find_or_create_by
(
subject:
section
[
:subj
],
course_number:
section
[
:code
])
...
...
@@ -50,6 +53,8 @@ total.each do |subject|
section_name
=
"
#{
section
[
:subj
]
}
#{
section
[
:code
]
}
#{
section
[
:sect
]
}
"
puts
"Adding
#{
section_name
}
..."
# the start and end times are located in the "time" key and look like START_TIME - END_TIME
# so, split them by the dash and add them
start_time
=
if
section
.
key?
"time"
section
[
"time"
].
split
(
' - '
).
first
else
...
...
@@ -76,6 +81,8 @@ total.each do |subject|
end
end
# create closures for the days there will be no classes
# see: https://registrar.gmu.edu/calendars/fall-2018/
Closure
.
create!
date:
Date
.
new
(
2018
,
9
,
3
),
semester:
semester
Closure
.
create!
date:
Date
.
new
(
2018
,
10
,
8
),
semester:
semester
(
21
..
25
).
each
{
|
n
|
Closure
.
create!
date:
Date
.
new
(
2018
,
11
,
n
),
semester:
semester
}
...
...
schedules_api/test/controllers/home_controller_test.rb
deleted
100644 → 0
View file @
91a2305f
require
'test_helper'
class
HomeControllerTest
<
ActionDispatch
::
IntegrationTest
test
'should get index'
do
get
home_index_url
assert_response
:success
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment