sakai-info 0.4.6 → 0.5.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.
@@ -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