sakai-info 0.4.6 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,23 @@
1
1
  # sakai-info Change History #
2
2
 
3
+ ### 0.5.0 ###
4
+
5
+ *Released 2012-06-21*
6
+
7
+ * Added --full-children and --all options for content
8
+ * Refactored rake tasks into one directory
9
+ * Added a barebones proof-of-concept-only query mode
10
+ * Removed quiz, quiz-section, and quiz-item types due to ID overlap risk
11
+ * Added new special modes to intercept calls for these removed object types
12
+ * Added published-quiz and pending-quiz types
13
+ * Added published-quiz-section and pending-quiz-section types
14
+ * Added published-quiz-item and pending-quiz-item types
15
+ * Added --texts record listing to published-quiz-item and pending-quiz-item
16
+ * Added --items record listing to quiz
17
+ * Better summaries for items in quiz-sections and quizzes
18
+ * Added a number of command line object type shortcuts
19
+ * Added random test fixture generation for user objects
20
+
3
21
  ### 0.4.6 ###
4
22
 
5
23
  *Released 2012-05-19*
data/LICENSE CHANGED
@@ -6,3 +6,8 @@ domain. This applies worldwide.
6
6
  I grant any entity the right to use this work for any purpose, without
7
7
  any conditions, unless such conditions are required by law.
8
8
 
9
+ If you require a fuller legal statement, please refer to the Creative
10
+ Commons Zero license:
11
+
12
+ http://creativecommons.org/publicdomain/zero/1.0/
13
+
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # sakai-info #
2
2
 
3
- last updated: 2012-05-19
3
+ last updated: 2012-06-21
4
4
  author: David Adams (daveadams@gmail.com)
5
5
  github url: https://github.com/daveadams/sakai-info
6
6
 
@@ -24,7 +24,7 @@ Use `rake` to test and build the gem:
24
24
  $ rake gem:build
25
25
 
26
26
  The resulting gem will be saved to the working directory as
27
- `sakai-info-0.4.6.gem`.
27
+ `sakai-info-0.5.0.gem`.
28
28
 
29
29
  Cleanup built gems using:
30
30
 
data/ROADMAP.md CHANGED
@@ -1,18 +1,43 @@
1
1
  # sakai-info Roadmap #
2
2
 
3
- *Last updated 2012-05-14*
3
+ *Last updated 2012-06-21*
4
4
 
5
- ### 0.5.x ###
5
+ ### 0.5.1 ###
6
+
7
+ * Test fixture generation for sites
8
+ * Text fixture loading into sqlite for sites and users
9
+ * Field restrictions for lookups
10
+ * Simple queries by name/title for user/site
11
+ * More details for quizzes including access control data
12
+
13
+ ### 0.5.2 ###
14
+
15
+ * Test fixture generation for quizzes
16
+ * Better date formatting all around
17
+ * More query functionality
18
+ * Proof-of-concept sin shell
19
+ * Chat channel/message support
20
+
21
+ ### 0.5.3 ###
22
+
23
+ * RDS schema creation and data loading for MySQL and Oracle testing
24
+ * More query and shell functionality
25
+
26
+ ### 0.5.4 ###
27
+
28
+ * Barebones web interface - HTML, JSON, and YAML
29
+ * More query and shell functionality
30
+
31
+ ### 0.5.5 ###
6
32
 
7
- * Sqlite test infrastructure for a few basic objects
8
- * Deeper query functionality and field specification
9
- * Simple web query interface - HTML, JSON, and YAML
10
33
  * Global cache instead of per-class
34
+ * More query, web, and shell functionality
11
35
 
12
36
  ### 0.6.x ###
13
37
 
14
38
  * Test fixtures and basic unit tests for all represented objects
15
39
  * RDoc coverage for every class
40
+ * More focus on other library usage besides ad hoc CLI/web queries
16
41
 
17
42
  ### 0.7.x ###
18
43
 
data/bin/sin CHANGED
@@ -5,7 +5,7 @@
5
5
  # sakai-info library
6
6
  #
7
7
  # Created 2012-02-15 daveadams@gmail.com
8
- # Last updated 2012-04-02 daveadams@gmail.com
8
+ # Last updated 2012-05-23 daveadams@gmail.com
9
9
  #
10
10
  # https://github.com/daveadams/sakai-info
11
11
  #
@@ -104,11 +104,16 @@ when "test" then
104
104
  exit
105
105
  end
106
106
 
107
+ when "query" then
108
+ mode = args.shift
109
+
107
110
  else
108
- # test to see if it's an accepted object mode
109
- if CLI::ObjectModes.keys.include? args[0]
110
- mode = args.shift
111
- id = args.shift
111
+ # test to see if it's an accepted lookup mode
112
+ if CLI::LookupModes.keys.include? args[0]
113
+ mode = "lookup"
114
+
115
+ elsif CLI::SpecialModes.keys.include? args[0]
116
+ mode = "special"
112
117
 
113
118
  else
114
119
  STDERR.puts "ERROR: Command '#{args[0]}' was not recognized."
@@ -136,18 +141,15 @@ if not db_name.nil?
136
141
  DB.default_database = db_name
137
142
  end
138
143
 
139
- if flags.include? "--all"
140
- serials = CLI::ObjectModes[mode].all_serializations
141
- elsif
142
- flags.include? "--dbrow-only"
143
- serials = [:dbrow_only]
144
- else
145
- serials = [:default] + flags.collect{|flag|flag.gsub(/^--/,'').gsub("-","_").to_sym}
146
- end
147
- begin
148
- puts CLI::ObjectModes[mode].find(id).to_yaml(serials)
149
- rescue ObjectNotFoundException
150
- STDERR.puts "ERROR: Could not find #{mode} with an ID of '#{id}'"
151
- exit 1
144
+ case mode
145
+ when "query"
146
+ CLI::Query.process(args, flags)
147
+
148
+ when "lookup"
149
+ CLI::Lookup.process(args, flags)
150
+
151
+ when "special"
152
+ CLI::Special.process(args, flags)
153
+
152
154
  end
153
155
 
@@ -2,7 +2,7 @@
2
2
  # Base library file
3
3
  #
4
4
  # Created 2012-02-15 daveadams@gmail.com
5
- # Last updated 2012-05-10 daveadams@gmail.com
5
+ # Last updated 2012-05-20 daveadams@gmail.com
6
6
  #
7
7
  # https://github.com/daveadams/sakai-info
8
8
  #
@@ -17,109 +17,10 @@ require 'sequel'
17
17
  require 'logger'
18
18
 
19
19
  require 'sakai-info/version'
20
-
21
- module SakaiInfo
22
- # base exception class for distinguishing SakaiInfo exceptions
23
- # from Ruby exceptions
24
- class SakaiException < Exception; end
25
-
26
- # exception to be raised when an object of a certain type cannot be found
27
- class ObjectNotFoundException < SakaiException
28
- def initialize(classname, identifier)
29
- @classname = classname
30
- @identifier = identifier
31
-
32
- super("Could not find a #{@classname} object for '#{@identifier}'")
33
- end
34
- end
35
-
36
- class Util
37
- # misc support functions
38
- FILESIZE_LABELS = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
39
- def self.format_filesize(i_size)
40
- size = i_size.to_f
41
- negative = false
42
-
43
- if size < 0
44
- negative = true
45
- size = -size
46
- end
47
-
48
- label = 0
49
- (FILESIZE_LABELS.size - 1).times do
50
- if size >= 1024.0
51
- size = size / 1024.0
52
- label += 1
53
- end
54
- end
55
-
56
- if size >= 100.0 or label == 0
57
- "#{negative ? "-" : ""}#{size.to_i.to_s} #{FILESIZE_LABELS[label]}"
58
- else
59
- "#{negative ? "-" : ""}#{sprintf("%.1f", size)} #{FILESIZE_LABELS[label]}"
60
- end
61
- end
62
-
63
- def self.format_entity_date(raw)
64
- if raw =~ /^(....)(..)(..)(..)(..)(..).*$/
65
- # I believe these are usually in UTC
66
- Time.utc($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i).getlocal
67
- else
68
- raw
69
- end
70
- end
71
- end
72
-
73
- # cache control
74
- class Cache
75
- def self.clear_all
76
- SakaiObject.descendants.select { |klass|
77
- klass.methods.include? :clear_cache
78
- }.each { |klass|
79
- klass.clear_cache
80
- }
81
- end
82
- end
83
- end
84
-
85
- ######################################################################
86
- # extensions to other objects
87
- class String
88
- def is_uuid?
89
- self =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
90
- end
91
- end
92
-
93
- # terrible hack to work around mysql case issues
94
- # essentially, if the hash key asked for is a symbol and its value is
95
- # nil, then try again with the uppercased version of the symbol
96
- # this might cause problems in weird cases with other hashes, this is
97
- # definitely not a sustainable fix.
98
- # TODO: patch Sequel for case-insensitive/-fixed identifiers
99
- class Hash
100
- alias :original_brackets :[]
101
-
102
- def [](key)
103
- if not (value = original_brackets(key)).nil?
104
- return value
105
- else
106
- if key.is_a? Symbol
107
- return original_brackets(key.to_s.upcase.to_sym)
108
- else
109
- return nil
110
- end
111
- end
112
- end
113
- end
114
-
115
- # alias .to_s on Blob class to match OCI8 blob class's read method
116
- module Sequel
117
- module SQL
118
- class Blob
119
- alias :read :to_s
120
- end
121
- end
122
- end
20
+ require 'sakai-info/exceptions'
21
+ require 'sakai-info/hacks'
22
+ require 'sakai-info/util'
23
+ require 'sakai-info/cache'
123
24
 
124
25
  # baseline db connectivity
125
26
  require 'sakai-info/database'
@@ -0,0 +1,23 @@
1
+ # cache.rb
2
+ # Cache control
3
+ #
4
+ # Created 2012-05-20 daveadams@gmail.com
5
+ # Last updated 2012-05-20 daveadams@gmail.com
6
+ #
7
+ # https://github.com/daveadams/sakai-info
8
+ #
9
+ # This software is public domain.
10
+ #
11
+
12
+ module SakaiInfo
13
+ # cache control
14
+ class Cache
15
+ def self.clear_all
16
+ SakaiObject.descendants.select { |klass|
17
+ klass.methods.include? :clear_cache
18
+ }.each { |klass|
19
+ klass.clear_cache
20
+ }
21
+ end
22
+ end
23
+ end
@@ -2,7 +2,7 @@
2
2
  # - sakai-info command line tool support
3
3
  #
4
4
  # Created 2012-02-19 daveadams@gmail.com
5
- # Last updated 2012-05-14 daveadams@gmail.com
5
+ # Last updated 2012-06-21 daveadams@gmail.com
6
6
  #
7
7
  # https://github.com/daveadams/sakai-info
8
8
  #
@@ -10,40 +10,10 @@
10
10
  #
11
11
 
12
12
  require 'sakai-info/cli/help'
13
+ require 'sakai-info/cli/lookup'
14
+ require 'sakai-info/cli/query'
15
+ require 'sakai-info/cli/special'
13
16
 
14
17
  # it's faster to run single-threaded
15
18
  Sequel.single_threaded = true
16
19
 
17
- module SakaiInfo
18
- class CLI
19
- ObjectModes = {
20
- "site" => Site,
21
- "page" => Page,
22
- "tool" => Tool,
23
- "user" => User,
24
- "group" => Group,
25
- "quiz" => Quiz,
26
- "quiz-section" => QuizSection,
27
- "quiz-item" => QuizItem,
28
- "quiz-attempt" => QuizAttempt,
29
- "quiz-attempt-item" => QuizAttemptItem,
30
- "quiz-attempt-item-attachment" => QuizAttemptItemAttachment,
31
- "qpool" => QuestionPool,
32
- "question-pool" => QuestionPool,
33
- "assignment" => Assignment,
34
- "assignment-submission" => AssignmentSubmission,
35
- "forum" => Forum,
36
- "forum-thread" => ForumThread,
37
- "forum-post" => ForumPost,
38
- "content" => Content,
39
- "announcement" => Announcement,
40
- "announcement-channel" => AnnouncementChannel,
41
- "gradebook" => Gradebook,
42
- "gradebook-item" => GradebookItem,
43
- "role" => AuthzRole,
44
- "function" => AuthzFunction,
45
- "realm" => AuthzRealm,
46
- }
47
- end
48
- end
49
-
@@ -2,7 +2,7 @@
2
2
  # - sin command line help
3
3
  #
4
4
  # Created 2012-02-19 daveadams@gmail.com
5
- # Last updated 2012-05-14 daveadams@gmail.com
5
+ # Last updated 2012-06-21 daveadams@gmail.com
6
6
  #
7
7
  # https://github.com/daveadams/sakai-info
8
8
  #
@@ -10,26 +10,42 @@
10
10
  #
11
11
 
12
12
  module SakaiInfo
13
- class CLI
13
+ module CLI
14
14
  class Help
15
15
  STRINGS = {
16
16
  :default => <<EOF,
17
- Sakai Info: sin #{VERSION}
18
- Usage: sin <command> [<id>] [<options>]
17
+ Sakai INfo: sin #{VERSION}
18
+
19
+ LOOKUP MODE
20
+ Lookup details about a particular object and its child objects.
21
+
22
+ Usage: sin <object-type> [<id>] [<options>]
23
+
24
+ Supported object types:
25
+ user, group, site, page, tool, pending-quiz, published-quiz,
26
+ pending-quiz-section, published-quiz-section, pending-quiz-item,
27
+ published-quiz-item, quiz-attempt, quiz-attempt-item,
28
+ quiz-attempt-item-attachment, question-pool, assignment,
29
+ assignment-submission, forum, forum-thread, forum-post, content,
30
+ announcement, announcement-channel, gradebook, gradebook-item,
31
+ role, function, realm
32
+
33
+ QUERY MODE
34
+ Query particular fields from certain objects given certain conditions.
35
+
36
+ Usage: sin query <object-type> [<options>]
19
37
 
20
- Object commands:
21
- user, group, site, page, tool, quiz, quiz-section, quiz-item, quiz-attempt,
22
- quiz-attempt-item, quiz-attempt-item-attachment, question-pool, assignment,
23
- assignment-submission, forum, forum-thread, forum-post, content,
24
- announcement, announcement-channel, gradebook, gradebook-item, role,
25
- function, realm
38
+ Use "sin help query" for more details.
26
39
 
27
- Misc commands:
28
- test Tests configured database connections
29
- help Prints general help
30
- version Prints version
31
- help <command> Prints help about a particular command
32
- help options Prints help about additional options
40
+ OTHER COMMANDS
41
+ Usage: sin <command>
42
+
43
+ Available commands:
44
+ test Tests configured database connections
45
+ help Prints general help
46
+ version Prints version
47
+ help <command> Prints help about a particular command
48
+ help options Prints help about additional options
33
49
  EOF
34
50
 
35
51
  "options" => <<EOF,
@@ -151,45 +167,78 @@ sin tool
151
167
  Prints information about the tool ID specified.
152
168
  EOF
153
169
 
154
- "quiz" => <<EOF,
155
- sin quiz
170
+ "pending-quiz" => <<EOF,
171
+ sin pending-quiz
156
172
 
157
- Usage: sin quiz <id> [<options>]
173
+ Usage: sin pending-quiz <id> [<options>]
158
174
 
159
- Prints information about the quiz ID specified. The quiz ID may represent
160
- a pending quiz or a published quiz. Additional options may be passed to
161
- include additional information:
175
+ Prints information about the pending quiz ID specified. Additional options
176
+ may be passed to include additional information:
162
177
 
163
178
  --sections Print section summary list
164
- --attempts Print summary of user quiz attempts
179
+ --items Print summary of items on the quiz
165
180
  --mod Print creation/modification info
181
+ EOF
166
182
 
167
- Not yet implemented:
183
+ "published-quiz" => <<EOF,
184
+ sin published-quiz
185
+
186
+ Usage: sin published-quiz <id> [<options>]
187
+
188
+ Prints information about the published quiz ID specified. Additional options
189
+ may be passed to include additional information:
190
+
191
+ --sections Print section summary list
168
192
  --items Print summary of items on the quiz
193
+ --attempts Print summary of user quiz attempts
194
+ --mod Print creation/modification info
169
195
  EOF
170
196
 
171
- "quiz-section" => <<EOF,
172
- sin quiz-section
197
+ "pending-quiz-section" => <<EOF,
198
+ sin pending-quiz-section
173
199
 
174
- Usage: sin quiz-section <id> [<options>]
200
+ Usage: sin pending-quiz-section <id> [<options>]
175
201
 
176
- Prints information about the quiz section ID specified. The ID may represent
177
- a pending quiz section or a published quiz section. Additional options may be
178
- passed to include additional information:
202
+ Prints information about the pending quiz section ID specified. Additional
203
+ options may be passed to include additional information:
179
204
 
180
205
  --items Print summary of items in the section
181
206
  --mod Print creation/modification info
182
207
  EOF
183
208
 
184
- "quiz-item" => <<EOF,
185
- sin quiz-item
209
+ "published-quiz-section" => <<EOF,
210
+ sin published-quiz-section
186
211
 
187
- Usage: sin quiz-item <id> [<options>]
212
+ Usage: sin published-quiz-section <id> [<options>]
188
213
 
189
- Prints information about the quiz item ID specified. The ID may represent
190
- a pending quiz item or a published quiz item. Additional options may be
191
- passed to include additional information:
214
+ Prints information about the published quiz section ID specified. Additional
215
+ options may be passed to include additional information:
192
216
 
217
+ --items Print summary of items in the section
218
+ --mod Print creation/modification info
219
+ EOF
220
+
221
+ "pending-quiz-item" => <<EOF,
222
+ sin pending-quiz-item
223
+
224
+ Usage: sin pending-quiz-item <id> [<options>]
225
+
226
+ Prints information about the pending quiz item ID specified. Additional
227
+ options may be passed to include additional information:
228
+
229
+ --texts List associated pending-quiz-item-text records
230
+ --mod Print creation/modification info
231
+ EOF
232
+
233
+ "published-quiz-item" => <<EOF,
234
+ sin published-quiz-item
235
+
236
+ Usage: sin published-quiz-item <id> [<options>]
237
+
238
+ Prints information about the published quiz item ID specified. Additional
239
+ options may be passed to include additional information:
240
+
241
+ --texts List associated quiz-item-text records
193
242
  --mod Print creation/modification info
194
243
  EOF
195
244
 
@@ -311,9 +360,10 @@ sin content
311
360
  Prints information about the content resource or collection ID specified.
312
361
  Additional options may be passed to include additional information:
313
362
 
314
- --properties Print all properties
315
- --children Recursively print collection children
316
- --mod Print creation/modification info
363
+ --properties Print all properties
364
+ --children Recursively print collection children
365
+ --full-children Print children with full IDs and file paths
366
+ --mod Print creation/modification info
317
367
  EOF
318
368
  "announcement" => <<EOF,
319
369
  sin announcement
@@ -378,6 +428,13 @@ sin realm
378
428
  --roles List roles associated with this realm
379
429
  --users List users in this realm
380
430
  --mod Print creation/modification info
431
+ EOF
432
+ "query" => <<EOF,
433
+ sin query
434
+
435
+ Usage: sin query <object-type> [<options>]
436
+
437
+ Query mode is only being tested at present.
381
438
  EOF
382
439
  }
383
440
 
@@ -0,0 +1,96 @@
1
+ # lookup.rb
2
+ # class for handling the default command line mode
3
+ #
4
+ # Created 2012-05-23 daveadams@gmail.com
5
+ # Last updated 2012-06-21 daveadams@gmail.com
6
+ #
7
+ # https://github.com/daveadams/sakai-info
8
+ #
9
+ # This software is public domain.
10
+ #
11
+
12
+ module SakaiInfo
13
+ module CLI
14
+ class Lookup
15
+ def self.process(args, flags)
16
+ object_type = args.shift
17
+ id = args.shift
18
+
19
+ if flags.include? "--all"
20
+ serials = CLI::LookupModes[object_type].all_serializations
21
+ elsif
22
+ flags.include? "--dbrow-only"
23
+ serials = [:dbrow_only]
24
+ else
25
+ serials = [:default] + flags.collect{|flag|flag.gsub(/^--/,'').gsub("-","_").to_sym}
26
+ end
27
+ begin
28
+ puts CLI::LookupModes[object_type].find(id).to_yaml(serials)
29
+ rescue ObjectNotFoundException
30
+ STDERR.puts "ERROR: Could not find #{object_type} with an ID of '#{id}'"
31
+ exit 1
32
+ end
33
+ end
34
+ end
35
+
36
+ LookupModes = {
37
+ "site" => Site,
38
+ "page" => Page,
39
+ "tool" => Tool,
40
+ "user" => User,
41
+ "group" => Group,
42
+ "pq" => PublishedQuiz,
43
+ "pubquiz" => PublishedQuiz,
44
+ "published-quiz" => PublishedQuiz,
45
+ "nq" => PendingQuiz,
46
+ "penquiz" => PendingQuiz,
47
+ "pending-quiz" => PendingQuiz,
48
+ "pqs" => PublishedQuizSection,
49
+ "pubqs" => PublishedQuizSection,
50
+ "published-quiz-section" => PublishedQuizSection,
51
+ "nqs" => PendingQuizSection,
52
+ "penqs" => PendingQuizSection,
53
+ "pending-quiz-section" => PendingQuizSection,
54
+ "pqi" => PublishedQuizItem,
55
+ "pubqi" => PublishedQuizItem,
56
+ "published-quiz-item" => PublishedQuizItem,
57
+ "nqi" => PendingQuizItem,
58
+ "penqi" => PendingQuizItem,
59
+ "pending-quiz-item" => PendingQuizItem,
60
+ "qa" => QuizAttempt,
61
+ "quiz-attempt" => QuizAttempt,
62
+ "qai" => QuizAttemptItem,
63
+ "quiz-attempt-item" => QuizAttemptItem,
64
+ "qaia" => QuizAttemptItemAttachment,
65
+ "quiz-attempt-item-attachment" => QuizAttemptItemAttachment,
66
+ "qp" => QuestionPool,
67
+ "qpool" => QuestionPool,
68
+ "question-pool" => QuestionPool,
69
+ "a" => Assignment,
70
+ "assignment" => Assignment,
71
+ "as" => AssignmentSubmission,
72
+ "asub" => AssignmentSubmission,
73
+ "assignment-submission" => AssignmentSubmission,
74
+ "forum" => Forum,
75
+ "ft" => ForumThread,
76
+ "forum-thread" => ForumThread,
77
+ "fp" => ForumPost,
78
+ "forum-post" => ForumPost,
79
+ "content" => Content,
80
+ "ann" => Announcement,
81
+ "announcement" => Announcement,
82
+ "annchan" => AnnouncementChannel,
83
+ "announcement-channel" => AnnouncementChannel,
84
+ "gb" => Gradebook,
85
+ "gradebook" => Gradebook,
86
+ "gbi" => GradebookItem,
87
+ "gradebook-item" => GradebookItem,
88
+ "role" => AuthzRole,
89
+ "fn" => AuthzFunction,
90
+ "func" => AuthzFunction,
91
+ "function" => AuthzFunction,
92
+ "realm" => AuthzRealm,
93
+ }
94
+ end
95
+ end
96
+
@@ -0,0 +1,58 @@
1
+ # query.rb
2
+ # class for handling "query" command line mode
3
+ #
4
+ # Created 2012-05-23 daveadams@gmail.com
5
+ # Last updated 2012-06-21 daveadams@gmail.com
6
+ #
7
+ # https://github.com/daveadams/sakai-info
8
+ #
9
+ # This software is public domain.
10
+ #
11
+
12
+ module SakaiInfo
13
+ module CLI
14
+ class Query
15
+ def self.process(args, flags)
16
+ # TODO: expand query functionality from proof of concept
17
+ object_type = args.shift
18
+ fields = []
19
+ site_id = nil
20
+
21
+ if object_type == "quiz"
22
+ flags.each do |flag|
23
+ case flag
24
+ when /^--fields=/
25
+ fields = flag.split("=")[1].split(",")
26
+ when /^--site=/
27
+ site_id = flag.split("=")[1]
28
+ else
29
+ STDERR.puts "ERROR: Unrecognized query flag"
30
+ exit 1
31
+ end
32
+ end
33
+
34
+ if site_id.nil?
35
+ STDERR.puts "ERROR: No site ID was provided"
36
+ exit 1
37
+ end
38
+
39
+ puts PublishedQuiz.find_ids_by_site_id(site_id)
40
+
41
+ elsif object_type == "userid" or object_type == "user_id" or object_type == "uid"
42
+ id = args.shift
43
+ begin
44
+ user = User.find(id)
45
+ puts user.id
46
+ rescue ObjectNotFoundException => e
47
+ STDERR.puts "ERROR: #{e}"
48
+ exit 1
49
+ end
50
+
51
+ else
52
+ STDERR.puts "ERROR: Unrecognized object type"
53
+ exit 1
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,32 @@
1
+ # special.rb
2
+ # class for handling the special command line modes
3
+ #
4
+ # Created 2012-06-21 daveadams@gmail.com
5
+ # Last updated 2012-06-21 daveadams@gmail.com
6
+ #
7
+ # https://github.com/daveadams/sakai-info
8
+ #
9
+ # This software is public domain. See LICENSE.
10
+ #
11
+
12
+ module SakaiInfo
13
+ module CLI
14
+ class Special
15
+ def self.process(args, flags)
16
+ mode = args.shift
17
+
18
+ STDERR.puts "WARNING:"
19
+ STDERR.puts " Looking up items of type '#{mode}' can return ambiguous results."
20
+ STDERR.puts " Please use #{SpecialModes[mode]} types instead."
21
+ exit 1
22
+ end
23
+ end
24
+
25
+ SpecialModes = {
26
+ "quiz" => "'published-quiz' or 'pending-quiz'",
27
+ "quiz-section" => "'published-quiz-section' or 'pending-quiz-section'",
28
+ "quiz-item" => "'published-quiz-item' or 'pending-quiz-item'",
29
+ }
30
+ end
31
+ end
32
+
@@ -2,7 +2,7 @@
2
2
  # SakaiInfo::Content library
3
3
  #
4
4
  # Created 2012-02-17 daveadams@gmail.com
5
- # Last updated 2012-05-14 daveadams@gmail.com
5
+ # Last updated 2012-05-20 daveadams@gmail.com
6
6
  #
7
7
  # https://github.com/daveadams/sakai-info
8
8
  #
@@ -140,6 +140,16 @@ module SakaiInfo
140
140
  self.binary_entity
141
141
  self.original_mod_details_serialization
142
142
  end
143
+
144
+ def self.all_serializations
145
+ [
146
+ :default,
147
+ :children,
148
+ :realm,
149
+ :properties,
150
+ :mod
151
+ ]
152
+ end
143
153
  end
144
154
 
145
155
  class ContentResource < Content
@@ -218,6 +228,14 @@ module SakaiInfo
218
228
  "size" => self.size_on_disk,
219
229
  }
220
230
  end
231
+
232
+ def detailed_summary_serialization
233
+ {
234
+ "id" => self.id,
235
+ "size" => self.size_on_disk,
236
+ "file_path" => self.file_path,
237
+ }
238
+ end
221
239
  end
222
240
 
223
241
  class ContentCollection < Content
@@ -319,6 +337,13 @@ module SakaiInfo
319
337
  }
320
338
  end
321
339
 
340
+ def detailed_summary_serialization
341
+ {
342
+ "id" => self.id,
343
+ "size_on_disk" => self.size_on_disk,
344
+ }
345
+ end
346
+
322
347
  def children_serialization
323
348
  result = {
324
349
  "collections" => self.children["collections"].collect { |cc|
@@ -337,6 +362,24 @@ module SakaiInfo
337
362
  result
338
363
  end
339
364
 
365
+ def full_children_serialization
366
+ result = {
367
+ "collections" => self.children["collections"].collect { |cc|
368
+ cc.serialize(:detailed_summary, :full_children)
369
+ },
370
+ "resources" => self.children["resources"].collect { |cr|
371
+ cr.serialize(:detailed_summary)
372
+ }
373
+ }
374
+ if result["collections"] == []
375
+ result.delete("collections")
376
+ end
377
+ if result["resources"] == []
378
+ result.delete("resources")
379
+ end
380
+ result
381
+ end
382
+
340
383
  def self.query_by_parent(parent_id)
341
384
  DB.connect[:content_collection].where(:in_collection => parent_id)
342
385
  end
@@ -0,0 +1,27 @@
1
+ # exceptions.rb
2
+ # Special exception definitions
3
+ #
4
+ # Created 2012-05-20 daveadams@gmail.com
5
+ # Last updated 2012-05-20 daveadams@gmail.com
6
+ #
7
+ # https://github.com/daveadams/sakai-info
8
+ #
9
+ # This software is public domain.
10
+ #
11
+
12
+ module SakaiInfo
13
+ # base exception class for distinguishing SakaiInfo exceptions
14
+ # from Ruby exceptions
15
+ class SakaiException < Exception; end
16
+
17
+ # exception to be raised when an object of a certain type cannot be found
18
+ class ObjectNotFoundException < SakaiException
19
+ def initialize(classname, identifier)
20
+ @classname = classname
21
+ @identifier = identifier
22
+
23
+ super("Could not find a #{@classname} object for '#{@identifier}'")
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,49 @@
1
+ # hacks.rb
2
+ # Hacks necessary to work around problems in external libraries
3
+ #
4
+ # Created 2012-05-20 daveadams@gmail.com
5
+ # Last updated 2012-05-20 daveadams@gmail.com
6
+ #
7
+ # https://github.com/daveadams/sakai-info
8
+ #
9
+ # This software is public domain.
10
+ #
11
+
12
+ ######################################################################
13
+ # extensions to other objects
14
+ class String
15
+ def is_uuid?
16
+ self =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
17
+ end
18
+ end
19
+
20
+ # terrible hack to work around mysql case issues
21
+ # essentially, if the hash key asked for is a symbol and its value is
22
+ # nil, then try again with the uppercased version of the symbol
23
+ # this might cause problems in weird cases with other hashes, this is
24
+ # definitely not a sustainable fix.
25
+ # TODO: patch Sequel for case-insensitive/-fixed identifiers
26
+ class Hash
27
+ alias :original_brackets :[]
28
+
29
+ def [](key)
30
+ if not (value = original_brackets(key)).nil?
31
+ return value
32
+ else
33
+ if key.is_a? Symbol
34
+ return original_brackets(key.to_s.upcase.to_sym)
35
+ else
36
+ return nil
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ # alias .to_s on Blob class to match OCI8 blob class's read method
43
+ module Sequel
44
+ module SQL
45
+ class Blob
46
+ alias :read :to_s
47
+ end
48
+ end
49
+ end
@@ -2,7 +2,7 @@
2
2
  # SakaiInfo::Quiz library
3
3
  #
4
4
  # Created 2012-02-17 daveadams@gmail.com
5
- # Last updated 2012-05-10 daveadams@gmail.com
5
+ # Last updated 2012-06-21 daveadams@gmail.com
6
6
  #
7
7
  # https://github.com/daveadams/sakai-info
8
8
  #
@@ -113,11 +113,19 @@ module SakaiInfo
113
113
  end
114
114
 
115
115
  def section_count
116
- @section_count ||= QuizSection.count_by_quiz_id(@id)
116
+ @section_count ||= self.section_class.count_by_quiz_id(@id)
117
117
  end
118
118
 
119
119
  def sections
120
- @sections ||= QuizSection.find_by_quiz_id(@id)
120
+ @sections ||= self.section_class.find_by_quiz_id(@id)
121
+ end
122
+
123
+ def item_count
124
+ @item_count ||= self.item_class.count_by_quiz_id(self.id)
125
+ end
126
+
127
+ def items
128
+ @items ||= self.item_class.find_by_quiz_id(self.id)
121
129
  end
122
130
 
123
131
  def default_serialization
@@ -128,6 +136,7 @@ module SakaiInfo
128
136
  "status" => self.status,
129
137
  "type" => self.quiz_type,
130
138
  "section_count" => self.section_count,
139
+ "item_count" => self.item_count,
131
140
  "attempt_count" => nil
132
141
  }
133
142
  if not self.site.nil?
@@ -169,8 +178,18 @@ module SakaiInfo
169
178
  }
170
179
  end
171
180
 
181
+ def items_serialization
182
+ {
183
+ "items" => self.items.collect { |i| i.serialize(:quiz_summary) }
184
+ }
185
+ end
186
+
172
187
  def self.all_serializations
173
- [:default, :sections]
188
+ [
189
+ :default,
190
+ :sections,
191
+ :items,
192
+ ]
174
193
  end
175
194
  end
176
195
 
@@ -202,21 +221,30 @@ module SakaiInfo
202
221
  end
203
222
 
204
223
  def self.find_by_site_id(site_id)
205
- results = []
206
- PendingQuiz.query_by_site_id(site_id).all.each do |row|
224
+ PendingQuiz.query_by_site_id(site_id).all.collect do |row|
207
225
  @@cache[row[:id]] = PendingQuiz.new(row, site_id)
208
- results << @@cache[row[:id]]
209
226
  end
210
- results
211
227
  end
212
228
 
213
229
  def self.count_by_site_id(site_id)
214
230
  PendingQuiz.query_by_site_id(site_id).count
215
231
  end
216
232
 
233
+ def self.find_ids_by_site_id(site_id)
234
+ PendingQuiz.query_by_site_id(site_id).select(:id).all.collect { |row| row[:id] }
235
+ end
236
+
217
237
  def quiz_type
218
238
  "pending"
219
239
  end
240
+
241
+ def section_class
242
+ PendingQuizSection
243
+ end
244
+
245
+ def item_class
246
+ PendingQuizItem
247
+ end
220
248
  end
221
249
 
222
250
  class PublishedQuiz < Quiz
@@ -248,22 +276,31 @@ module SakaiInfo
248
276
  end
249
277
 
250
278
  def self.find_by_site_id(site_id)
251
- results = []
252
- PublishedQuiz.query_by_site_id(site_id).all.each do |row|
279
+ PublishedQuiz.query_by_site_id(site_id).all.collect do |row|
253
280
  @@cache[row[:id]] = PublishedQuiz.new(row, site_id)
254
- results << @@cache[row[:id]]
255
281
  end
256
- results
257
282
  end
258
283
 
259
284
  def self.count_by_site_id(site_id)
260
285
  PublishedQuiz.query_by_site_id(site_id).count
261
286
  end
262
287
 
288
+ def self.find_ids_by_site_id(site_id)
289
+ PublishedQuiz.query_by_site_id(site_id).select(:id).all.collect { |row| row[:id] }
290
+ end
291
+
263
292
  def quiz_type
264
293
  "published"
265
294
  end
266
295
 
296
+ def section_class
297
+ PublishedQuizSection
298
+ end
299
+
300
+ def item_class
301
+ PublishedQuizItem
302
+ end
303
+
267
304
  def attempt_count
268
305
  @attempt_count ||= QuizAttempt.count_by_quiz_id(self.id)
269
306
  end
@@ -279,7 +316,12 @@ module SakaiInfo
279
316
  end
280
317
 
281
318
  def self.all_serializations
282
- [:default, :sections, :attempts]
319
+ [
320
+ :default,
321
+ :sections,
322
+ :items,
323
+ :attempts,
324
+ ]
283
325
  end
284
326
  end
285
327
 
@@ -335,12 +377,7 @@ module SakaiInfo
335
377
  end
336
378
 
337
379
  def self.find_by_quiz_id(quiz_id)
338
- section_class = if Quiz.find(quiz_id).quiz_type == "pending"
339
- PendingQuizSection
340
- else
341
- PublishedQuizSection
342
- end
343
-
380
+ section_class = Quiz.find(quiz_id).section_class
344
381
  QuizSection.query_by_quiz_id(quiz_id).all.collect do |row|
345
382
  section_class.new(row)
346
383
  end
@@ -355,11 +392,11 @@ module SakaiInfo
355
392
  end
356
393
 
357
394
  def item_count
358
- @item_count ||= QuizItem.count_by_section_id(@id)
395
+ @item_count ||= QuizItem.count_by_section_id(self.id)
359
396
  end
360
397
 
361
398
  def items
362
- @items ||= QuizItem.find_by_section_id(@id)
399
+ @items ||= QuizItem.find_by_section_id(self.id)
363
400
  end
364
401
 
365
402
  def default_serialization
@@ -393,7 +430,7 @@ module SakaiInfo
393
430
 
394
431
  def items_serialization
395
432
  {
396
- "items" => self.items.collect{|i|i.serialize(:summary)}
433
+ "items" => self.items.collect{|i|i.serialize(:section_summary)}
397
434
  }
398
435
  end
399
436
 
@@ -534,15 +571,35 @@ module SakaiInfo
534
571
 
535
572
  def self.find_by_quiz_id(quiz_id)
536
573
  item_class = QuizItem.class_for_type(Quiz.find(quiz_id).quiz_type)
537
- QuizItem.query_by_quiz_id(quiz_id).all.collect do |row|
574
+ QuizItem.query_by_quiz_id(quiz_id).order(:sequence).all.collect do |row|
538
575
  item_class.new(row)
539
- end
576
+ end.sort { |a,b| if a.section.sequence == b.section.sequence
577
+ a.sequence <=> b.sequence
578
+ else
579
+ a.section.sequence <=> b.section.sequence
580
+ end }
540
581
  end
541
582
 
542
583
  def item_type
543
584
  nil
544
585
  end
545
586
 
587
+ def itemtext_table
588
+ nil
589
+ end
590
+
591
+ def texts
592
+ if self.itemtext_table.nil?
593
+ return []
594
+ end
595
+
596
+ DB.connect[self.itemtext_table].
597
+ select(:text).
598
+ where(:itemid => self.id).
599
+ order(:sequence).all.
600
+ collect { |row| row[:text].read }
601
+ end
602
+
546
603
  def default_serialization
547
604
  {
548
605
  "id" => self.id,
@@ -562,8 +619,33 @@ module SakaiInfo
562
619
  }
563
620
  end
564
621
 
622
+ def quiz_summary_serialization
623
+ {
624
+ "id" => self.id,
625
+ "section" => self.section.sequence,
626
+ "sequence" => self.sequence,
627
+ }
628
+ end
629
+
630
+ def section_summary_serialization
631
+ {
632
+ "id" => self.id,
633
+ "sequence" => self.sequence,
634
+ }
635
+ end
636
+
637
+ def texts_serialization
638
+ {
639
+ "texts" => self.texts
640
+ }
641
+ end
642
+
565
643
  def self.all_serializations
566
- [:default, :mod]
644
+ [
645
+ :default,
646
+ :mod,
647
+ :texts
648
+ ]
567
649
  end
568
650
  end
569
651
 
@@ -589,6 +671,10 @@ module SakaiInfo
589
671
  def item_type
590
672
  "pending"
591
673
  end
674
+
675
+ def itemtext_table
676
+ :sam_itemtext_t
677
+ end
592
678
  end
593
679
 
594
680
  class PublishedQuizItem < QuizItem
@@ -613,6 +699,10 @@ module SakaiInfo
613
699
  def item_type
614
700
  "published"
615
701
  end
702
+
703
+ def itemtext_table
704
+ :sam_publisheditemtext_t
705
+ end
616
706
  end
617
707
 
618
708
  # class QuizItemAttachment < SakaiObject
@@ -0,0 +1,50 @@
1
+ # util.rb
2
+ # Utility methods
3
+ #
4
+ # Created 2012-05-20 daveadams@gmail.com
5
+ # Last updated 2012-05-20 daveadams@gmail.com
6
+ #
7
+ # https://github.com/daveadams/sakai-info
8
+ #
9
+ # This software is public domain.
10
+ #
11
+
12
+ module SakaiInfo
13
+ class Util
14
+ # misc support functions
15
+ FILESIZE_LABELS = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
16
+ def self.format_filesize(i_size)
17
+ size = i_size.to_f
18
+ negative = false
19
+
20
+ if size < 0
21
+ negative = true
22
+ size = -size
23
+ end
24
+
25
+ label = 0
26
+ (FILESIZE_LABELS.size - 1).times do
27
+ if size >= 1024.0
28
+ size = size / 1024.0
29
+ label += 1
30
+ end
31
+ end
32
+
33
+ if size >= 100.0 or label == 0
34
+ "#{negative ? "-" : ""}#{size.to_i.to_s} #{FILESIZE_LABELS[label]}"
35
+ else
36
+ "#{negative ? "-" : ""}#{sprintf("%.1f", size)} #{FILESIZE_LABELS[label]}"
37
+ end
38
+ end
39
+
40
+ def self.format_entity_date(raw)
41
+ if raw =~ /^(....)(..)(..)(..)(..)(..).*$/
42
+ # I believe these are usually in UTC
43
+ Time.utc($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i).getlocal
44
+ else
45
+ raw
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -1,3 +1,3 @@
1
1
  module SakaiInfo
2
- VERSION = "0.4.6"
2
+ VERSION = "0.5.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sakai-info
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.6
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-19 00:00:00.000000000 Z
12
+ date: 2012-06-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sequel
@@ -52,10 +52,16 @@ extensions: []
52
52
  extra_rdoc_files: []
53
53
  files:
54
54
  - lib/sakai-info.rb
55
+ - lib/sakai-info/util.rb
55
56
  - lib/sakai-info/group.rb
56
57
  - lib/sakai-info/tool.rb
57
58
  - lib/sakai-info/cli/help.rb
59
+ - lib/sakai-info/cli/query.rb
60
+ - lib/sakai-info/cli/special.rb
61
+ - lib/sakai-info/cli/lookup.rb
58
62
  - lib/sakai-info/site.rb
63
+ - lib/sakai-info/cache.rb
64
+ - lib/sakai-info/exceptions.rb
59
65
  - lib/sakai-info/assignment.rb
60
66
  - lib/sakai-info/authz.rb
61
67
  - lib/sakai-info/gradebook.rb
@@ -64,6 +70,7 @@ files:
64
70
  - lib/sakai-info/sakai_object.rb
65
71
  - lib/sakai-info/quiz.rb
66
72
  - lib/sakai-info/user.rb
73
+ - lib/sakai-info/hacks.rb
67
74
  - lib/sakai-info/version.rb
68
75
  - lib/sakai-info/announcement.rb
69
76
  - lib/sakai-info/database.rb