stellar 0.2.0 → 0.3.0

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/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: