oncourse 0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4ff62a2167ee5451cf1f72b9b3ca9ff65ba210ae
4
+ data.tar.gz: e1c80d3865f1f2adb24fd4687e29cabdf7a25e1d
5
+ SHA512:
6
+ metadata.gz: 22bffbdf58374929970a8f62735ffadbc2c15bb418d0d0745e8b25e04dc37c708d6431919e031e6b3a69ed4642d7908ca769c33d755af7c0ce46481d8fca0709
7
+ data.tar.gz: a7e8f792a83abf9e48e74934948d69ba60578f60e679914f6046a7fe78984d3c6a6c955fa6b4c35af193d7c11869ae046a4ee673f25b5a3a13c3fa2ada836357
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in oncourse.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 steve <stevecd123@gmail.com>
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # Oncourse
2
+
3
+ Oncoursesystems.com is a web app written in ext3js that handles managing teacher
4
+ lesson plans amongst many other things. It has proven to be incredibly tedious to
5
+ work with so I spent a day parsing its html and json requests involved with lessonplan
6
+ editing. This gem is the result.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'oncourse'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install oncourse
21
+
22
+ ## Usage
23
+
24
+ require 'oncourse'
25
+ client = Oncourse::Client.new('testuser', 'password')
26
+
27
+ # parse the weekly planner for a specific date and number of weeks.
28
+ # in the school I'm helping, the weeks start on monday, so this date should
29
+ # be a monday. The week is that monday and the following 4 days.
30
+ # This method only parses the html, so it does not read the homework field.
31
+ # You'll only get the lesson. Use client.read_lesson if you want the homework.
32
+ lessonplan = client.read_planner(Date.parse("2014-03-10"), 1)
33
+
34
+ # After a client.read_planner call, the planner is stored in an instance variable.
35
+ # We can now read all of the standards for each lesson we've read so far:
36
+ lessonplan_with_standards = client.read_planner_standards()
37
+
38
+
39
+ # Save the lesson plan so far.
40
+ client.save_planner("./lessonplan_20140310.json")
41
+
42
+ # read a single lesson given a date and period.
43
+ # this method also returns homework
44
+ puts client.read_lesson(Date.today, 2)
45
+
46
+ # post a lesson
47
+ client.post_lesson("2 + 2 = 4", "homework: 3+3= ?", Date.today, 2)
48
+
49
+ ## Contributing
50
+
51
+ 1. Fork it ( http://github.com/stevecd/ruby-oncourse/fork )
52
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
53
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
54
+ 4. Push to the branch (`git push origin my-new-feature`)
55
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc "Open a pry session preloaded with this library"
4
+ task :console do
5
+ sh "pry -r rubygems -I lib -r oncourse.rb"
6
+ end
data/lib/oncourse.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "oncourse/version"
2
+ require "oncourse/client"
3
+
4
+ module Oncourse
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,239 @@
1
+ require 'mechanize'
2
+ module Oncourse
3
+ URLS = {
4
+ main: "http://www.oncoursesystems.com/",
5
+ set_planner: "http://www.oncoursesystems.com/planner/planner_frame.aspx", #date=20140310&user_id=user_id&template=N
6
+ get_planner: "http://www.oncoursesystems.com/planner/planner.aspx",
7
+ close_planner: "http://www.oncoursesystems.com/planner/planner_navigation_bar.aspx", #action=C&tabid=337113074
8
+ lesson_standards: "http://www.oncoursesystems.com/json.axd/lessonplan/references_linked", #post request, {id: <standards_id>}
9
+ link_standards: "http://www.oncoursesystems.com/json.axd/standards/link_standards",
10
+ read_standard_areas: "http://www.oncoursesystems.com/json.axd/standards/lesson_standard_areas", #post request, {setId: <standard_setId}
11
+ lesson_standard_filters: "http://www.oncoursesystems.com/json.axd/standards/lesson_standard_filters", #post request, {setId: <standard_setId>, subject: {standard_subject}}
12
+ lesson_standards_tree: "http://www.oncoursesystems.com/json.axd/standards/lesson_standards_tree",
13
+ read_lesson: "http://www.oncoursesystems.com/json.axd/LessonPlan/lesson_record",
14
+ post_lesson: "http://www.oncoursesystems.com/json.axd/LessonPlan/lesson_record_save",
15
+ lesson_tree: "http://www.oncoursesystems.com/json.axd/LessonPlan/lesson_tree" #{userId: <user_id>}
16
+ }
17
+ class Client
18
+ attr_accessor :mech, :user_id, :weeks, :request_rate, :lesson_tree
19
+
20
+ # create new mechanize object and go through login steps
21
+ def initialize(username,password,request_rate=1)
22
+ @request_rate = request_rate
23
+ @mech = Mechanize.new()
24
+ login_page = @mech.get(URLS[:main])
25
+ login_form = login_page.forms.first
26
+ login_form.Username = username
27
+ login_form.Password = password.strip
28
+ home_page = login_form.submit
29
+ script_tag = home_page.parser.search("script").select{|s| s.text.index('user_id')}.first
30
+ raise "Login failed, script_tag not present." unless script_tag
31
+ @user_id = /\"user_id\":\"([0-9]+)\"/.match(script_tag.text).captures.first
32
+ raise "Login failed, unable to find user_id" unless @user_id
33
+ end
34
+
35
+ # read lesson planner for current user starting at start_Date: start_date
36
+ # and going each week up to num_weeks
37
+ def read_planner(start_date, num_weeks)
38
+ @weeks = []
39
+ start = start_date
40
+ cell_ids = 0
41
+ itr = 0
42
+ num_weeks.times do
43
+ start += itr * 7
44
+ week = {start: start.strftime("%Y%m%d")}
45
+ week[:columns] = []
46
+
47
+ # a get request to this set_planner url sets the current viewed planner
48
+ @mech.get(URLS[:set_planner], {date: week[:start], user_id: @user_id, template:'N'})
49
+ rate_pause()
50
+
51
+ planner_frame = @mech.get(URLS[:get_planner])
52
+
53
+ # parse out column headers
54
+ columns = planner_frame.parser.search("tr.sheetRowHeader th:not(.sheetRow1stCell)").each_with_index.map{|cell, idx| {label: cell.text.strip, period: idx + 1}}
55
+
56
+ # parse out each cell, ids give date and period.
57
+ # Period corresponds to column number.
58
+ # .lessonPreview classes contain the markup and content created by ext3's text editor
59
+ # that teachers use to input lessons.
60
+ cells = planner_frame.parser.search(".sheetCell")
61
+ cells = cells.map do |cell|
62
+ cell_hash = {
63
+ date: cell['id'][0...-2],
64
+ period: cell['id'][-2..-1].to_i,
65
+ html: cell.search('.lessonPreview').first.inner_html
66
+ }
67
+ flag_element = cell.search('.sheetCellIcons img[src="/images/icons/flag_blue.png"]').first
68
+ if(flag_element)
69
+ match = /showStandard\(\'([0-9]+)\'\)/.match(flag_element['onclick'])
70
+ if(match && match.captures.first)
71
+ cell_hash[:standards_id] = match.captures.first
72
+ end
73
+ end
74
+ cell_hash
75
+ end
76
+ columns.each do |column|
77
+ column[:cells] = cells.select{|cell| cell[:period] == column[:period]}
78
+ end
79
+
80
+ week[:columns] = columns
81
+
82
+ @weeks << week
83
+ itr += 1
84
+ rate_pause()
85
+ end
86
+
87
+ return @weeks
88
+ end
89
+
90
+ def rate_pause
91
+ sleep((1 + rand(0.1)) * @request_rate)
92
+ end
93
+
94
+ def read_planner_standards()
95
+ raise "planner weeks empty" if !@weeks || @weeks.length == 0
96
+ keys = ["id", "header_name", "detail_name"]
97
+ @weeks.each do |week|
98
+ week[:columns].each do |column|
99
+ column[:cells].each do |cell|
100
+ next unless cell[:standards_id]
101
+
102
+ json_response = @mech.post(URLS[:lesson_standards], {
103
+ id: cell[:standards_id]
104
+ })
105
+
106
+ cell[:standards] = JSON.parse(json_response.content).map{|f| f.reject{|k,v| !keys.include?(k)}}
107
+ rate_pause()
108
+ end
109
+ end
110
+ end
111
+ return @weeks
112
+ end
113
+
114
+ def save_planner(filename)
115
+ File.open(filename, "w") { |f| f.puts JSON.pretty_generate(@weeks)}
116
+ end
117
+
118
+ # method to read one lesson through the json interface
119
+ # This is required before posting a lesson since it's the only way to get the
120
+ # homework field that I've found. If you post an empty homework field, any previous
121
+ # homework entry will be lost
122
+ def read_lesson(date, period)
123
+ response = @mech.post(URLS[:read_lesson], {
124
+ userId: @user_id,
125
+ date: date.strftime("%m/%d/%Y"),
126
+ period: period
127
+ })
128
+ return JSON.parse(response.content)["ReturnValue"]
129
+ end
130
+
131
+ def read_lesson_tree()
132
+ return @lesson_tree if @lesson_tree
133
+ response = @mech.post(URLS[:lesson_tree], {userId: @user_id})
134
+
135
+ # oncourse returns invalid JSON, using this ugly regex for now.
136
+ fixed_json = fix_malformed_json(response.content)
137
+
138
+ @lesson_tree = JSON.parse(fixed_json)
139
+
140
+ return @lesson_tree
141
+ end
142
+
143
+ # oncourse produces a lot of malformed json. It seems to strip the quotes off strings
144
+ # on deeply nested objects. Not sure what they're doing wrong, since I don't think
145
+ # the problem is with ext3
146
+ def fix_malformed_json(malformed_json)
147
+ return malformed_json.gsub(/([a-z][a-zA-Z0-9_]+):(?!\s)/, '"\1":')
148
+ end
149
+
150
+ # read parent groups for standards
151
+ def read_standard_groups()
152
+ read_lesson_tree unless @lesson_tree
153
+
154
+ #have to traverse a big json tree here
155
+ standards_branch = @lesson_tree.find{|branch| branch["text"] == "Standards"}
156
+ return standards_branch["children"].map do |child|
157
+ {
158
+ label: child["text"],
159
+ set_id: child["xconfig"]["setId"]
160
+ }
161
+ end
162
+ end
163
+
164
+ # reads first child group of standards
165
+ def read_standard_areas(set_id)
166
+ response = @mech.post(URLS[:standard_areas], {setId: set_id})
167
+
168
+ return JSON.parse(fix_malformed_json(response.content))
169
+ end
170
+
171
+ # reads filters that must be applied after selecting first child group
172
+ def read_lesson_standard_filters(set_id, subject)
173
+ response = @mech.post(URLS[:lesson_standard_filters], {setId: set_id, subject: subject})
174
+
175
+ return JSON.parse(response.content)
176
+ end
177
+
178
+ # Reads a full list of standards for a given SetID, subject, grade, and year
179
+ def read_lesson_standards_tree(set_id, subject, grade, year)
180
+ response = @mech.post(URLS[:lesson_standards_tree], {
181
+ userId: @user_id,
182
+ setId: set_id,
183
+ subject: subject,
184
+ yearName: year,
185
+ searchText1: "",
186
+ searchOperator: "",
187
+ searchText2: "",
188
+ powerSetID: "",
189
+ mapID: "",
190
+ grade: grade,
191
+ showOnlyPowerSet: false,
192
+ activityDate: Date.today.strftime("%m/%d/%Y"), # seems arbitrary
193
+ activityPeriod: 1 # arbitrary
194
+ })
195
+
196
+ return JSON.parse(response.content)
197
+ end
198
+
199
+ def link_standard(standard_id, date, period)
200
+ response = @mech.post(URLS[:link_standards], {
201
+ objectType: "L",
202
+ id: standard_id,
203
+ date: date.strftime("%m/%d/%Y"),
204
+ period: period,
205
+ link: true
206
+ })
207
+
208
+ return JSON.parse(response.content)
209
+ end
210
+
211
+ def unlink_standard(standard_id, date, period)
212
+ response = @mech.post(URLS[:link_standards], {
213
+ objectType: "L",
214
+ id: standard_id,
215
+ date: date.strftime("%m/%d/%Y"),
216
+ period: period,
217
+ link: false
218
+ })
219
+
220
+ return JSON.parse(response.content)
221
+ end
222
+
223
+ # This method will absolutely overwrite anything that occupies this place
224
+ # in the lesson plan. To play it safe, one should call read_lesson and store
225
+ # that response somewhere before using post lesson. This way there is a backup
226
+ # of previous work. Oncourse does not appear to keep a revision history on
227
+ # lesson plans.
228
+ def post_lesson(lesson, homework, date, period)
229
+ response = @mech.post(URLS[:post_lesson], {
230
+ userId: @user_id,
231
+ date: date.strftime("%m/%d/%Y"),
232
+ period: period,
233
+ notes: lesson,
234
+ homework: homework
235
+ })
236
+ return response
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,3 @@
1
+ module Oncourse
2
+ VERSION = "0.0.1"
3
+ end
data/oncourse.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'oncourse/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "oncourse"
8
+ spec.version = Oncourse::VERSION
9
+ spec.authors = ["steve"]
10
+ spec.email = ["stevecd123@gmail.com"]
11
+ spec.summary = %q{Provides a basic lessonplan API}
12
+ spec.description = %q{oncoursesystems.com's interface is very tedious to work with in a lot of cases, this gem uses mechanize to scrape and post lessonplan data}
13
+ spec.homepage = "https://github.com/stevecd/ruby-oncourse"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "pry"
23
+ spec.add_development_dependency "rake"
24
+
25
+ spec.add_runtime_dependency "mechanize"
26
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oncourse
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - steve
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
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: rake
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
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mechanize
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: oncoursesystems.com's interface is very tedious to work with in a lot
70
+ of cases, this gem uses mechanize to scrape and post lessonplan data
71
+ email:
72
+ - stevecd123@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - lib/oncourse.rb
83
+ - lib/oncourse/client.rb
84
+ - lib/oncourse/version.rb
85
+ - oncourse.gemspec
86
+ homepage: https://github.com/stevecd/ruby-oncourse
87
+ licenses:
88
+ - MIT
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.0.3
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Provides a basic lessonplan API
110
+ test_files: []