stellar 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
data/lib/stellar.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # Standard library.
2
+ require 'date'
2
3
  require 'json'
3
4
  require 'openssl'
4
5
  require 'uri'
@@ -23,3 +24,4 @@ require 'stellar/client.rb'
23
24
  require 'stellar/courses.rb'
24
25
  require 'stellar/gradebook.rb'
25
26
  require 'stellar/homework.rb'
27
+ require 'stellar/members.rb'
@@ -99,7 +99,7 @@ class Course
99
99
  raise ArgumentError, "#{course_url} is not a course page"
100
100
  end
101
101
  @navigation = Hash[navbar_elems.first.css('a').map do |link|
102
- [link.inner_text, URI.join(course_page.url, link['href'])]
102
+ [link.inner_text.strip, URI.join(course_page.url, link['href'])]
103
103
  end]
104
104
  end
105
105
 
@@ -112,6 +112,11 @@ class Course
112
112
  def gradebook
113
113
  @gradebook ||= Stellar::Gradebook.new self
114
114
  end
115
+
116
+ # Client scoped to the course's Members module.
117
+ def members
118
+ @members ||= Stellar::Members.new self
119
+ end
115
120
  end # class Stellar::Course
116
121
 
117
122
  end # namespace Stellar
@@ -23,8 +23,8 @@ class Gradebook
23
23
  @url = course.navigation['Gradebook']
24
24
 
25
25
  page = @client.get_nokogiri @url
26
- @navigation = Hash[page.css('#toolBox.dashboard dd a').map { |link|
27
- [link.inner_text, URI.join(page.url, link['href'])]
26
+ @navigation = Hash[page.css('#toolBox dd a').map { |link|
27
+ [link.inner_text.strip, URI.join(page.url, link['href'])]
28
28
  }]
29
29
  end
30
30
 
@@ -83,10 +83,13 @@ class Submission
83
83
  # URL to the last file that the student submitted.
84
84
  attr_reader :file_url
85
85
 
86
+ # Submission time.
87
+ attr_reader :time
88
+
86
89
  # Name of the student who authored this submission.
87
90
  attr_reader :name
88
91
 
89
- # Email of the student who authorted this submission.
92
+ # Email of the student who authored this submission.
90
93
  attr_reader :email
91
94
 
92
95
  # Comments posted on this submission.
@@ -117,7 +120,7 @@ class Submission
117
120
  page = @client.get_nokogiri @url
118
121
 
119
122
  unless author_link = page.css('#content h4 a[href^="mailto:"]').first
120
- raise ArgumentError, 'Invalud submission-listing <tr>'
123
+ raise ArgumentError, 'Invalid submission-listing <tr>'
121
124
  end
122
125
  @name = author_link.inner_text
123
126
  @email = author_link['href'].sub /^mailto:/, ''
@@ -125,6 +128,13 @@ class Submission
125
128
  next nil unless link.inner_text == homework.name
126
129
  URI.join @url.to_s, link['href']
127
130
  }.reject(&:nil?).first
131
+ @time = page.css('#rosterBox .instruction').map { |span|
132
+ unless span.css('strong').any? { |strong| /date/i =~ strong.inner_text }
133
+ next nil
134
+ end
135
+ time_string = span.inner_text.split(':', 2).last.strip
136
+ time = DateTime.parse(time_string + ' GMT-4').to_time
137
+ }.reject(&:nil?).first
128
138
 
129
139
  @add_comment_url = URI.join @url.to_s,
130
140
  page.css('#comments a[href*="add"]').first['href']
@@ -0,0 +1,142 @@
1
+ # :nodoc: namespace
2
+ module Stellar
3
+
4
+ # Stellar client scoped to a course's Members module.
5
+ class Members
6
+ # Maps the text in navigation links to URI objects.
7
+ #
8
+ # Example: navigation['Homework'] => <# URI: .../ >
9
+ attr_reader :navigation
10
+
11
+ # The course whose membership is exposed by this client.
12
+ attr_reader :course
13
+
14
+ # Generic Stellar client used to make requests.
15
+ attr_reader :client
16
+
17
+ # Creates a Stellar client scoped to a course's Membership module.
18
+ #
19
+ # @param [Stellar::Course] the course whose membership info is desired
20
+ def initialize(course)
21
+ @course = course
22
+ @client = course.client
23
+ @url = course.navigation['Membership']
24
+
25
+ page = @client.get_nokogiri @url
26
+ @navigation = Hash[page.css('#toolBox dd a').map { |link|
27
+ [link.inner_text.strip, URI.join(page.url, link['href'])]
28
+ }]
29
+ end
30
+
31
+ # All member photos in this course's Membership module.
32
+ # @return [Stellar::Gradebook::PhotoList] list of member photos for students
33
+ def photos
34
+ @students ||= Stellar::Members::PhotoList.new self
35
+ end
36
+
37
+ # Collection of member photos in a course's Membership module.
38
+ class PhotoList
39
+ # Client scoped to the Membership module supplying this list of photos.
40
+ attr_reader :course
41
+
42
+ # Generic Stellar client used to make requests.
43
+ attr_reader :client
44
+
45
+ # Creates a list of member photos for a class.
46
+ #
47
+ # @param [Stellar::Members] members client scoped to a course's membership
48
+ def initialize(members)
49
+ @course = members.course
50
+ @client = members.client
51
+
52
+ @url = members.navigation['Member Photos']
53
+ reload!
54
+ end
55
+
56
+ # All member photos listed in this course's Membership module.
57
+ # @return [Array<Stellar::Members::Photo>] photos of students in this course
58
+ def all
59
+ @photos
60
+ end
61
+
62
+ # A photo in the course's Membership module.
63
+ # @param [String] name the name of the desired member
64
+ # @return [Stellar::Members::Photo] a photo for a member with the given name,
65
+ # or nil if no such member exists
66
+ def named(name)
67
+ @photos.find { |a| a.name == name }
68
+ end
69
+
70
+ # A photo in the course's Membership module.
71
+ # @param [String] email the e-mail of the desired member
72
+ # @return [Stellar::Members::Photo] a photo for a member with the given name,
73
+ # or nil if no such member exists
74
+ def with_email(email)
75
+ @photos.find { |a| a.email == email }
76
+ end
77
+
78
+ # Reloads the contents of this student list.
79
+ #
80
+ # @return [Stellar::Gradebook::StudentList] self, for easy call chaining
81
+ def reload!
82
+ photo_page = @client.get_nokogiri @url
83
+
84
+ @photos = photo_page.css('#content .cols > div').map { |div|
85
+ begin
86
+ Stellar::Members::Photo.new div, @course
87
+ rescue ArgumentError
88
+ nil
89
+ end
90
+ }.reject(&:nil?)
91
+
92
+ self
93
+ end
94
+ end # class Stellar::Members::Photo
95
+
96
+ # Information about a member's photo
97
+ class Photo
98
+ # The member's full name.
99
+ attr_reader :name
100
+
101
+ # The member's e-mail.
102
+ attr_reader :email
103
+
104
+ # URL of the member's photo.
105
+ attr_reader :url
106
+
107
+ # The course this member belongs to.
108
+ attr_reader :course
109
+
110
+ # Generic Stellar client used to make requests.
111
+ attr_reader :client
112
+
113
+ # Creates a Stellar client scoped to a student's Gradebook page.
114
+ #
115
+ # @param [Nokogiri::XML::Element] div a <div> representing a member's photo
116
+ # and info in the Members Photo page
117
+ # @param [Stellar::Course] course Stellar client scoped to the
118
+ # course that this student belongs to
119
+ def initialize(div, course)
120
+ @course = course
121
+ @client = course.client
122
+
123
+ unless img = div.css('img[src*="pictures"]').first
124
+ raise ArgumentError, 'Invalid photo-listing <div>'
125
+ end
126
+ @url = URI.join div.document.url, img['src'].gsub('/half/', '/full/')
127
+ unless mail_link = div.css('a[href*="mailto"]').first
128
+ raise ArgumentError, 'Invalid photo-listing <div>'
129
+ end
130
+ @email = mail_link['href'].sub(/^mailto\:/, '')
131
+ @name = mail_link.inner_text
132
+ end
133
+
134
+ # The member's photo bits.
135
+ def data
136
+ @client.get_file @url
137
+ end
138
+ end # class Stellar::Members::Photo
139
+
140
+ end # class Stellar::Members
141
+
142
+ end # namespace Stellar
@@ -25,10 +25,12 @@ describe Stellar::Courses do
25
25
  end
26
26
 
27
27
  describe Stellar::Course do
28
- let(:six) { test_client.course("6.006", 2011, :fall) }
28
+ before :all do
29
+ @six = test_client.course("6.006", 2011, :fall)
30
+ end
29
31
 
30
32
  describe '#navigation' do
31
- let(:nav) { six.navigation }
33
+ let(:nav) { @six.navigation }
32
34
 
33
35
  it 'should have a Gradebook link' do
34
36
  nav['Gradebook'].should be_kind_of(URI)
@@ -37,5 +39,9 @@ describe Stellar::Course do
37
39
  it 'should have a Homework link' do
38
40
  nav['Homework'].should be_kind_of(URI)
39
41
  end
42
+
43
+ it 'should have a Membership link' do
44
+ nav['Membership'].should be_kind_of(URI)
45
+ end
40
46
  end
41
47
  end
@@ -14,12 +14,15 @@ describe Stellar::Homework::Submission do
14
14
  it "should have the author's email" do
15
15
  one.email.reverse.should == 'ude.tim@ytsirhc'
16
16
  end
17
+ it 'should have a submission time' do
18
+ one.time.to_s.should == '2011-09-15 19:55:00 -0400'
19
+ end
17
20
  it 'should have a submission URL' do
18
21
  one.file_url.should be_kind_of(URI)
19
22
  end
20
23
  it 'should have the right bits in the submission' do
21
24
  one.file_data.should match(/\% 6\.006/)
22
- end
25
+ end
23
26
 
24
27
  it 'should have at least one feedback comment' do
25
28
  one.comments.should have_at_least(1).comment
@@ -0,0 +1,47 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Stellar::Members do
4
+ before :all do
5
+ @members = test_client.course("6.006", 2011, :fall).members
6
+ end
7
+
8
+ describe '#navigation' do
9
+ it 'should have a Member Photos section' do
10
+ @members.navigation['Member Photos'].should be_kind_of(URI)
11
+ end
12
+ end
13
+
14
+ describe '#photos' do
15
+ before :all do
16
+ @photos = @members.photos
17
+ end
18
+
19
+ describe '#all' do
20
+ let(:all) { @photos.all}
21
+ end
22
+
23
+ shared_examples_for 'a photo' do
24
+ it "should have the member's name" do
25
+ one.name.should == 'Christianne B. Swartz'
26
+ end
27
+ it "should have the member's email" do
28
+ one.email.reverse.should == 'ude.tim@ytsirhc'
29
+ end
30
+ it 'should have a photo URL' do
31
+ one.url.should be_kind_of(URI)
32
+ end
33
+ it 'should have the right bits in the photo' do
34
+ one.data.should match(/JFIF/)
35
+ end
36
+ end
37
+
38
+ describe '#named' do
39
+ let(:one) { @photos.named('Christianne B. Swartz') }
40
+ it_should_behave_like 'a photo'
41
+ end
42
+ describe '#with_email' do
43
+ let(:one) { @photos.with_email('ude.tim@ytsirhc'.reverse) }
44
+ it_should_behave_like 'a photo'
45
+ end
46
+ end
47
+ end
@@ -19,6 +19,6 @@ end
19
19
 
20
20
  class TestCredentialsCache
21
21
  def self.test_client
22
- @__test_client ||= Stellar::Client.new.auth :kerberos => test_mit_kerberos
22
+ @__test_client ||= Stellar::Client.new.auth :cert => test_mit_cert
23
23
  end
24
24
  end
data/stellar.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "stellar"
8
- s.version = "0.2.0"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Victor Costan"]
12
- s.date = "2011-10-28"
12
+ s.date = "2011-10-31"
13
13
  s.description = "So we don't have to put up with Stellar's craptastic ui"
14
14
  s.email = "victor@costan.us"
15
15
  s.extra_rdoc_files = [
@@ -32,6 +32,7 @@ Gem::Specification.new do |s|
32
32
  "lib/stellar/courses.rb",
33
33
  "lib/stellar/gradebook.rb",
34
34
  "lib/stellar/homework.rb",
35
+ "lib/stellar/members.rb",
35
36
  "lib/stellar/mitca.crt",
36
37
  "spec/fixtures/.gitkeep",
37
38
  "spec/spec_helper.rb",
@@ -42,6 +43,7 @@ Gem::Specification.new do |s|
42
43
  "spec/stellar/gradebook_student_spec.rb",
43
44
  "spec/stellar/homework_spec.rb",
44
45
  "spec/stellar/homework_submissions_spec.rb",
46
+ "spec/stellar/members_spec.rb",
45
47
  "spec/stellar_spec.rb",
46
48
  "spec/support/test-credentials.rb",
47
49
  "stellar.gemspec"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stellar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-28 00:00:00.000000000Z
12
+ date: 2011-10-31 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mechanize
16
- requirement: &19824800 !ruby/object:Gem::Requirement
16
+ requirement: &12124280 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 2.0.2
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *19824800
24
+ version_requirements: *12124280
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: nokogiri
27
- requirement: &19823840 !ruby/object:Gem::Requirement
27
+ requirement: &12123720 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 1.5.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *19823840
35
+ version_requirements: *12123720
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rdoc
38
- requirement: &19812300 !ruby/object:Gem::Requirement
38
+ requirement: &12123100 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 3.9.4
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *19812300
46
+ version_requirements: *12123100
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rspec
49
- requirement: &19811720 !ruby/object:Gem::Requirement
49
+ requirement: &12122440 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 2.6.0
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *19811720
57
+ version_requirements: *12122440
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: yard
60
- requirement: &19811080 !ruby/object:Gem::Requirement
60
+ requirement: &12121800 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 0.7.2
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *19811080
68
+ version_requirements: *12121800
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: yard-rspec
71
- requirement: &19810280 !ruby/object:Gem::Requirement
71
+ requirement: &12121120 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0.1'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *19810280
79
+ version_requirements: *12121120
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: bundler
82
- requirement: &19809620 !ruby/object:Gem::Requirement
82
+ requirement: &12120540 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: 1.0.21
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *19809620
90
+ version_requirements: *12120540
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: jeweler
93
- requirement: &19809040 !ruby/object:Gem::Requirement
93
+ requirement: &12119600 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,10 +98,10 @@ dependencies:
98
98
  version: 1.6.4
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *19809040
101
+ version_requirements: *12119600
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: rcov
104
- requirement: &19808300 !ruby/object:Gem::Requirement
104
+ requirement: &12114640 !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
107
  - - ! '>='
@@ -109,10 +109,10 @@ dependencies:
109
109
  version: '0'
110
110
  type: :development
111
111
  prerelease: false
112
- version_requirements: *19808300
112
+ version_requirements: *12114640
113
113
  - !ruby/object:Gem::Dependency
114
114
  name: ruby-debug
115
- requirement: &19806780 !ruby/object:Gem::Requirement
115
+ requirement: &12113840 !ruby/object:Gem::Requirement
116
116
  none: false
117
117
  requirements:
118
118
  - - ! '>='
@@ -120,10 +120,10 @@ dependencies:
120
120
  version: '0'
121
121
  type: :development
122
122
  prerelease: false
123
- version_requirements: *19806780
123
+ version_requirements: *12113840
124
124
  - !ruby/object:Gem::Dependency
125
125
  name: ruby-debug19
126
- requirement: &19805900 !ruby/object:Gem::Requirement
126
+ requirement: &12112920 !ruby/object:Gem::Requirement
127
127
  none: false
128
128
  requirements:
129
129
  - - ! '>='
@@ -131,10 +131,10 @@ dependencies:
131
131
  version: '0'
132
132
  type: :development
133
133
  prerelease: false
134
- version_requirements: *19805900
134
+ version_requirements: *12112920
135
135
  - !ruby/object:Gem::Dependency
136
136
  name: highline
137
- requirement: &19804800 !ruby/object:Gem::Requirement
137
+ requirement: &12111740 !ruby/object:Gem::Requirement
138
138
  none: false
139
139
  requirements:
140
140
  - - ! '>='
@@ -142,10 +142,10 @@ dependencies:
142
142
  version: 1.6.2
143
143
  type: :development
144
144
  prerelease: false
145
- version_requirements: *19804800
145
+ version_requirements: *12111740
146
146
  - !ruby/object:Gem::Dependency
147
147
  name: json
148
- requirement: &19793180 !ruby/object:Gem::Requirement
148
+ requirement: &12110680 !ruby/object:Gem::Requirement
149
149
  none: false
150
150
  requirements:
151
151
  - - ! '>='
@@ -153,7 +153,7 @@ dependencies:
153
153
  version: '0'
154
154
  type: :development
155
155
  prerelease: false
156
- version_requirements: *19793180
156
+ version_requirements: *12110680
157
157
  description: So we don't have to put up with Stellar's craptastic ui
158
158
  email: victor@costan.us
159
159
  executables: []
@@ -177,6 +177,7 @@ files:
177
177
  - lib/stellar/courses.rb
178
178
  - lib/stellar/gradebook.rb
179
179
  - lib/stellar/homework.rb
180
+ - lib/stellar/members.rb
180
181
  - lib/stellar/mitca.crt
181
182
  - spec/fixtures/.gitkeep
182
183
  - spec/spec_helper.rb
@@ -187,6 +188,7 @@ files:
187
188
  - spec/stellar/gradebook_student_spec.rb
188
189
  - spec/stellar/homework_spec.rb
189
190
  - spec/stellar/homework_submissions_spec.rb
191
+ - spec/stellar/members_spec.rb
190
192
  - spec/stellar_spec.rb
191
193
  - spec/support/test-credentials.rb
192
194
  - stellar.gemspec
@@ -205,7 +207,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
205
207
  version: '0'
206
208
  segments:
207
209
  - 0
208
- hash: -1478260872774288007
210
+ hash: -1213779627807188058
209
211
  required_rubygems_version: !ruby/object:Gem::Requirement
210
212
  none: false
211
213
  requirements: