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 +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +55 -0
- data/Rakefile +6 -0
- data/lib/oncourse.rb +6 -0
- data/lib/oncourse/client.rb +239 -0
- data/lib/oncourse/version.rb +3 -0
- data/oncourse.gemspec +26 -0
- metadata +110 -0
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
data/Gemfile
ADDED
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
data/lib/oncourse.rb
ADDED
@@ -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
|
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: []
|