rtunesu 0.2.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.
- data/History.txt +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +77 -0
- data/README.txt +38 -0
- data/Rakefile +4 -0
- data/config/hoe.rb +75 -0
- data/config/requirements.rb +15 -0
- data/lib/multipart.rb +53 -0
- data/lib/rtunesu/connection.rb +111 -0
- data/lib/rtunesu/document.rb +84 -0
- data/lib/rtunesu/entities/course.rb +8 -0
- data/lib/rtunesu/entities/division.rb +16 -0
- data/lib/rtunesu/entities/group.rb +8 -0
- data/lib/rtunesu/entities/permission.rb +7 -0
- data/lib/rtunesu/entities/section.rb +15 -0
- data/lib/rtunesu/entities/site.rb +7 -0
- data/lib/rtunesu/entities/theme.rb +4 -0
- data/lib/rtunesu/entities/track.rb +13 -0
- data/lib/rtunesu/entity.rb +154 -0
- data/lib/rtunesu/user.rb +26 -0
- data/lib/rtunesu/version.rb +9 -0
- data/lib/rtunesu.rb +25 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +82 -0
- data/setup.rb +1585 -0
- data/spec/connection_spec.rb +53 -0
- data/spec/document_spec.rb +16 -0
- data/spec/documents/add_spec.rb +41 -0
- data/spec/documents/delete_spec.rb +30 -0
- data/spec/documents/merge_spec.rb +30 -0
- data/spec/documents/show_tree_spec.rb +16 -0
- data/spec/entities/course_spec.rb +47 -0
- data/spec/entities/division_spec.rb +8 -0
- data/spec/entities/group_spec.rb +8 -0
- data/spec/entities/permission_spec.rb +8 -0
- data/spec/entities/section_spec.rb +8 -0
- data/spec/entities/site_spec.rb +8 -0
- data/spec/entities/track_spec.rb +8 -0
- data/spec/entity_spec.rb +108 -0
- data/spec/fixtures/add_course.xml +26 -0
- data/spec/fixtures/add_division.xml +26 -0
- data/spec/fixtures/add_group.xml +27 -0
- data/spec/fixtures/add_permission.xml +12 -0
- data/spec/fixtures/add_section.xml +34 -0
- data/spec/fixtures/add_track.xml +19 -0
- data/spec/fixtures/delete_course.xml +8 -0
- data/spec/fixtures/delete_division.xml +8 -0
- data/spec/fixtures/delete_group.xml +8 -0
- data/spec/fixtures/delete_permission.xml +9 -0
- data/spec/fixtures/delete_section.xml +8 -0
- data/spec/fixtures/delete_track.xml +7 -0
- data/spec/fixtures/merge_course.xml +38 -0
- data/spec/fixtures/merge_division.xml +47 -0
- data/spec/fixtures/merge_group.xml +29 -0
- data/spec/fixtures/merge_permission.xml +12 -0
- data/spec/fixtures/merge_section.xml +36 -0
- data/spec/fixtures/merge_site.xml +31 -0
- data/spec/fixtures/merge_track.xml +18 -0
- data/spec/fixtures/requests/add_coures_request.xml +0 -0
- data/spec/fixtures/responses/generic_entity_response.xml +17 -0
- data/spec/fixtures/responses/show_tree_course.xml +676 -0
- data/spec/fixtures/show_tree.xml +273 -0
- data/spec/fixtures/update_group.xml +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/user_spec.rb +18 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/tasks/rspec.rake +21 -0
- data/tasks/website.rake +17 -0
- data/website/index.html +141 -0
- data/website/index.txt +83 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +138 -0
- data/website/template.html.erb +48 -0
- metadata +144 -0
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'hpricot'
|
2
|
+
|
3
|
+
module RTunesU
|
4
|
+
# A Base class reprenseting the various entities seen in iTunes U. Subclassed into the actual entity classes (Course, Division, Track, etc). Entity is mostly an object oriented interface to the underlying XML data returned from iTunes U. Most of attributes of an Entity are read by searching the souce XML returned from iTunes U by the Entity class's implemention of method_missing.
|
5
|
+
# Attribute of an Entity are written through method missing as well. Methods that end in '=' will write data that will be saved to iTunes U.
|
6
|
+
# == Reading and Writing Attributes
|
7
|
+
# c = Course.find(12345, rtunes_connection_object) # finds the Course in iTunes U and stores its XML data
|
8
|
+
# c.Handle # finds the <Handle> element in the XML data and returns its value (in this case 12345)
|
9
|
+
# c.Name # finds the <Name> element in the XML data and returns its value (e.g. 'My Example Course')
|
10
|
+
# c.Name = 'Podcasting: a Revolution' # writes a hash of unsaved data that will be sent to iTunes U.
|
11
|
+
#
|
12
|
+
# == Accessing related entities
|
13
|
+
# Related Entity objects are accessed with the pluralized form of their class name. To access a Course's related Group entities, you would use c.Groups. This will return an array of Group objects (or an empty Array object if there are no associated Groups)
|
14
|
+
# You can set the array of associated entities by using the '=' form of the accessor and add anothe element to the end of an array of related entities with '<<'
|
15
|
+
# Examples:
|
16
|
+
# c = Course.find(12345, rtunes_connection_object) # finds the Course in iTunes U and stores its XML data
|
17
|
+
# c.Groups # returns an array of Group entities or an empty array of there are no Group entities
|
18
|
+
# c.Groups = [Group.new(:Name => 'Lectures')] # assigns the Groups related entity array to an existign array (overwriting any local data about Groups)
|
19
|
+
# c.Groups << Group.new(:Name => 'Videos') # Adds the new Group object to the end of hte Groups array
|
20
|
+
# c.Groups.collect {|g| g.Name} # ['Lectures', 'Videos']
|
21
|
+
#
|
22
|
+
# == Notes on arbitrary XML
|
23
|
+
# Because Entity is, at heart, an object oriented wrapper for iTunes U XML data it is possible to add arbitrary (and possibly meaningless or invalidating) data that will be sent to iTunes U. You should have a solid understanding of how Entites relate in iTunes U to avoind sending bad data.
|
24
|
+
# Examples:
|
25
|
+
# c = Course.find(12345, rtunes_connection_object)
|
26
|
+
# c.Junk = 'some junk xml'
|
27
|
+
# c.save
|
28
|
+
# # c.save will generate XML that inclucdes
|
29
|
+
# # <Course>
|
30
|
+
# # <Junk>some junk xml</Junk>
|
31
|
+
# # ... some other XML data ...
|
32
|
+
# # </Course>
|
33
|
+
# # this XML may raise errors in iTunes U because it doesn't match valid iTunes U documents
|
34
|
+
class Entity
|
35
|
+
attr_accessor :connection, :attributes, :handle, :parent, :parent_handle, :saved, :source_xml
|
36
|
+
|
37
|
+
# Creates a new Entity object with attributes based on the hash argument Some of these attributes are assgined to instance variables of the obect (if there is an attr_accessor for it), the rest will be written to a hash of edits that will be saved to iTunes U using method_missing
|
38
|
+
def initialize(attrs = {})
|
39
|
+
self.attributes = {}
|
40
|
+
attrs.each {|attribute, value| self.send("#{attribute}=", value)}
|
41
|
+
end
|
42
|
+
|
43
|
+
# Finds a specific entity in iTunes U. To find an entity you will need to know both its type (Course, Group, etc) and handle. Handles uniquely identify entities in iTunes U and the entity type is used to search the returned XML for the specific entity you are looking for. For example,
|
44
|
+
# Course.find(123456, rtunes_connection_object)
|
45
|
+
def self.find(handle, connection)
|
46
|
+
entity = self.new(:handle => handle)
|
47
|
+
entity.load_from_xml(connection.process(Document::ShowTree.new(entity).xml))
|
48
|
+
entity
|
49
|
+
end
|
50
|
+
|
51
|
+
def load_from_xml(xml)
|
52
|
+
self.source_xml = Hpricot.XML(xml).at("//ITunesUResponse//#{self.class_name}//Handle[text()=#{self.handle}]..")
|
53
|
+
raise EntityNotFound if self.source_xml.nil?
|
54
|
+
end
|
55
|
+
|
56
|
+
# Edits stores the changes made to an entity
|
57
|
+
def edits
|
58
|
+
@edits ||= {}
|
59
|
+
end
|
60
|
+
|
61
|
+
# Clear the edits and restores the loaded object to its original form
|
62
|
+
def reload
|
63
|
+
self.edits.clear
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the parent of the entity
|
67
|
+
def parent
|
68
|
+
@parent ||= Object.module_eval(self.source_xml.parent.name).new(:source_xml => self.source_xml.parent)
|
69
|
+
rescue
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def method_missing(method_name, args = nil)
|
74
|
+
# introspect the kind of method call (read one attribute, read an array of related items, write one attribute, write an array of related items)
|
75
|
+
case method_name.to_s.match(/(s)*(=)*$/).captures
|
76
|
+
when [nil, "="] : self.edits[method_name.to_s[0..-2]] = args
|
77
|
+
when ["s", "="] : self.edits[method_name.to_s[0..-2]] = args
|
78
|
+
when [nil, nil] : value_from_edits_or_store(method_name.to_s)
|
79
|
+
when ["s", nil] : value_from_edits_or_store(method_name.to_s, true)
|
80
|
+
end
|
81
|
+
rescue NoMethodError
|
82
|
+
raise NoMethodError, "undefined method '#{method_name}' for #{self.class}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def value_from_edits_or_store(name, multi = false)
|
86
|
+
if multi
|
87
|
+
begin
|
88
|
+
self.edits[name] || (self.source_xml / name.to_s.chop).collect {|el| Object.module_eval(el.name).new(:source_xml => el)}
|
89
|
+
rescue NoMethodError
|
90
|
+
self.edits[name] = []
|
91
|
+
end
|
92
|
+
else
|
93
|
+
self.edits[name] || (self.source_xml % name).innerHTML
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
# Returns the name of the object's class ignoring namespacing.
|
99
|
+
# === Use:
|
100
|
+
# course = RTunesU::Course.new
|
101
|
+
# course.class #=> 'RTunesU::Course'
|
102
|
+
# course.class_name #=> 'Course'
|
103
|
+
def class_name
|
104
|
+
self.class.to_s.split(':').last
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns the handle of the entitiy's parent. This can either be set directly as a string or interger or will access the parent entity. Sometimes you know the parent_handle without the parent object (for example, stored locally from an earlier request). This allows you to add a new Entity to iTunes U without first firing a reques for a prent entity (For example, if your institution places all inside the same Section, you want to add a new Section to your Site, or a new Group to a course tied to your institution's LMS).
|
108
|
+
def parent_handle
|
109
|
+
self.parent ? self.parent.handle : @parent_handle
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_xml(xml_builder = Builder::XmlMarkup.new)
|
113
|
+
xml_builder.tag!(self.class_name) {
|
114
|
+
self.edits.each {|attribute,edit| edit.is_a?(Array) ? edit.each {|item| item.to_xml(xml_builder)} : xml_builder.tag!(attribute, edit) }
|
115
|
+
# self.edits.each {|attribute| xml_builder.tag!(attribute.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }, self.attributes[attribute.to_s]) unless self.attributes[attribute.to_s].nil? || self.attributes[attribute.to_s].empty? }
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
def update(connection)
|
120
|
+
connection.process(Document::Merge.new(self).xml)
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
def create(connection)
|
125
|
+
response = Hpricot.XML(connection.process(Document::Add.new(self).xml))
|
126
|
+
raise Exception, response.at('error').innerHTML if response.at('error')
|
127
|
+
self.handle = response.at('AddedObjectHandle').innerHTML
|
128
|
+
self
|
129
|
+
end
|
130
|
+
|
131
|
+
# Saves the entity to iTunes U. Save takes single argument (an iTunes U connection object). If the entity is unsaved this will create the entity and populate its handle attribte. If the entity has already been saved it will send the updated data (if any) to iTunes U.
|
132
|
+
def save(connection)
|
133
|
+
saved? ? update(connection) : create(connection)
|
134
|
+
end
|
135
|
+
|
136
|
+
def saved?
|
137
|
+
self.handle ? true : false
|
138
|
+
end
|
139
|
+
|
140
|
+
# Deletes the entity from iTunes U. This cannot be undone.
|
141
|
+
def delete(connection)
|
142
|
+
response = Hpricot.XML(connection.process(Document::Delete.new(self).xml))
|
143
|
+
raise Exception, response.at('error').innerHTML if response.at('error')
|
144
|
+
self.handle = nil
|
145
|
+
self
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class EntityNotFound < Exception
|
150
|
+
end
|
151
|
+
|
152
|
+
class MissingParent < Exception
|
153
|
+
end
|
154
|
+
end
|
data/lib/rtunesu/user.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module RTunesU
|
2
|
+
# Represents a User for iTunes U authentication. Requests to iTunes U require User information in a specific format. This class exists to access data in that specific format. Best combined with a User object from your own authentication or LMS system.
|
3
|
+
# Webservices requests to iTunes U require your institutions administrator credentials
|
4
|
+
# === User
|
5
|
+
# u = RTunesU::User.new('191912121', 'jsmith', 'John Smith', 'jsmith@exmaple.edu')
|
6
|
+
# u.credentials = ['Instructor@urn:mace:example.edu', 'Learner@urn:mace:example.edu']
|
7
|
+
# u.to_identity_string #=> '"John Smith" <jsmith@example.edu> (jsmith) [191912121]'
|
8
|
+
# u.to_credential_string #=> 'Instructor@urn:mace:example.edu;Learner@urn:mace:example.edu'
|
9
|
+
# === Interaction with other classes
|
10
|
+
# a User object is required for creating a Connection object
|
11
|
+
class User
|
12
|
+
attr_accessor :name, :email, :username, :id, :credentials
|
13
|
+
|
14
|
+
def initialize(id, username, name, email)
|
15
|
+
self.id, self.username, self.name, self.email = id, username, name, email
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_identity_string
|
19
|
+
'"%s" <%s> (%s) [%s]' % [name, email, username, id]
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_credential_string
|
23
|
+
self.credentials.join(';')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/rtunesu.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'multipart'
|
5
|
+
|
6
|
+
require 'rtunesu/version'
|
7
|
+
require 'rtunesu/connection'
|
8
|
+
require 'rtunesu/user'
|
9
|
+
require 'rtunesu/document'
|
10
|
+
require 'rtunesu/entity'
|
11
|
+
require 'rtunesu/entities/course'
|
12
|
+
require 'rtunesu/entities/division'
|
13
|
+
require 'rtunesu/entities/group'
|
14
|
+
require 'rtunesu/entities/permission'
|
15
|
+
require 'rtunesu/entities/section'
|
16
|
+
require 'rtunesu/entities/site'
|
17
|
+
require 'rtunesu/entities/track'
|
18
|
+
require 'rtunesu/entities/theme'
|
19
|
+
|
20
|
+
module RTunesU
|
21
|
+
API_URL = 'https://deimos.apple.com/WebObjects/Core.woa/API'
|
22
|
+
API_VERSION = '1.1.1'
|
23
|
+
BROWSE_URL = 'https://deimos.apple.com/WebObjects/Core.woa/Browse'
|
24
|
+
SHOW_TREE_URL = 'https://deimos.apple.com/WebObjects/Core.woa/API/ShowTree'
|
25
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/rtunesu.rb'}"
|
9
|
+
puts "Loading rtunesu gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
data/script/txt2html
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
GEM_NAME = 'rtunesu' # what ppl will type to install your gem
|
4
|
+
RUBYFORGE_PROJECT = 'rtunesu'
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
begin
|
8
|
+
require 'newgem'
|
9
|
+
require 'rubyforge'
|
10
|
+
rescue LoadError
|
11
|
+
puts "\n\nGenerating the website requires the newgem RubyGem"
|
12
|
+
puts "Install: gem install newgem\n\n"
|
13
|
+
exit(1)
|
14
|
+
end
|
15
|
+
require 'redcloth'
|
16
|
+
require 'syntax/convertors/html'
|
17
|
+
require 'erb'
|
18
|
+
require File.dirname(__FILE__) + "/../lib/#{GEM_NAME}/version.rb"
|
19
|
+
|
20
|
+
version = RTunesU::VERSION::STRING
|
21
|
+
download = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
|
22
|
+
|
23
|
+
def rubyforge_project_id
|
24
|
+
RubyForge.new.autoconfig["group_ids"][RUBYFORGE_PROJECT]
|
25
|
+
end
|
26
|
+
|
27
|
+
class Fixnum
|
28
|
+
def ordinal
|
29
|
+
# teens
|
30
|
+
return 'th' if (10..19).include?(self % 100)
|
31
|
+
# others
|
32
|
+
case self % 10
|
33
|
+
when 1: return 'st'
|
34
|
+
when 2: return 'nd'
|
35
|
+
when 3: return 'rd'
|
36
|
+
else return 'th'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Time
|
42
|
+
def pretty
|
43
|
+
return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def convert_syntax(syntax, source)
|
48
|
+
return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
|
49
|
+
end
|
50
|
+
|
51
|
+
if ARGV.length >= 1
|
52
|
+
src, template = ARGV
|
53
|
+
template ||= File.join(File.dirname(__FILE__), '/../website/template.html.erb')
|
54
|
+
else
|
55
|
+
puts("Usage: #{File.split($0).last} source.txt [template.html.erb] > output.html")
|
56
|
+
exit!
|
57
|
+
end
|
58
|
+
|
59
|
+
template = ERB.new(File.open(template).read)
|
60
|
+
|
61
|
+
title = nil
|
62
|
+
body = nil
|
63
|
+
File.open(src) do |fsrc|
|
64
|
+
title_text = fsrc.readline
|
65
|
+
body_text_template = fsrc.read
|
66
|
+
body_text = ERB.new(body_text_template).result(binding)
|
67
|
+
syntax_items = []
|
68
|
+
body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
|
69
|
+
ident = syntax_items.length
|
70
|
+
element, syntax, source = $1, $2, $3
|
71
|
+
syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
|
72
|
+
"syntax-temp-#{ident}"
|
73
|
+
}
|
74
|
+
title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
|
75
|
+
body = RedCloth.new(body_text).to_html
|
76
|
+
body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
|
77
|
+
end
|
78
|
+
stat = File.stat(src)
|
79
|
+
created = stat.ctime
|
80
|
+
modified = stat.mtime
|
81
|
+
|
82
|
+
$stdout << template.result(binding)
|