aladdin 0.0.1 → 0.0.3

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.
Files changed (49) hide show
  1. data/README.md +16 -10
  2. data/assets/favicon.ico +0 -0
  3. data/assets/images/graphic.png +0 -0
  4. data/assets/images/no_gravatar.gif +0 -0
  5. data/assets/javascripts/app.js +76 -0
  6. data/bin/aladdin +2 -17
  7. data/lib/aladdin.rb +58 -13
  8. data/lib/aladdin/app.rb +27 -7
  9. data/lib/aladdin/cli.rb +29 -0
  10. data/lib/aladdin/commands/new.rb +16 -0
  11. data/lib/aladdin/commands/server.rb +10 -0
  12. data/lib/aladdin/mixin/logger.rb +26 -0
  13. data/lib/aladdin/mixin/weak_comparator.rb +60 -0
  14. data/lib/aladdin/render/error.rb +20 -0
  15. data/lib/aladdin/render/image.rb +54 -0
  16. data/lib/aladdin/render/markdown.rb +111 -8
  17. data/lib/aladdin/render/multi.rb +40 -0
  18. data/lib/aladdin/render/navigation.rb +34 -0
  19. data/lib/aladdin/render/problem.rb +114 -0
  20. data/lib/aladdin/render/sanitize.rb +3 -1
  21. data/lib/aladdin/render/short.rb +30 -0
  22. data/lib/aladdin/render/table.rb +109 -0
  23. data/lib/aladdin/render/template.rb +32 -0
  24. data/lib/aladdin/submission.rb +92 -0
  25. data/lib/aladdin/version.rb +1 -1
  26. data/skeleton/images/graphic.png +0 -0
  27. data/skeleton/index.md +3 -0
  28. data/views/haml/exe.haml +5 -0
  29. data/views/haml/img.haml +6 -0
  30. data/views/haml/layout.haml +44 -12
  31. data/views/haml/multi.haml +18 -0
  32. data/views/haml/nav.haml +5 -0
  33. data/views/haml/short.haml +14 -0
  34. data/views/haml/table.haml +30 -0
  35. data/views/scss/app.scss +50 -44
  36. data/views/scss/mathjax.scss +5 -0
  37. data/views/scss/pygment.scss +9 -6
  38. metadata +47 -18
  39. data/assets/images/foundation/orbit/bullets.jpg +0 -0
  40. data/assets/images/foundation/orbit/left-arrow-small.png +0 -0
  41. data/assets/images/foundation/orbit/left-arrow.png +0 -0
  42. data/assets/images/foundation/orbit/loading.gif +0 -0
  43. data/assets/images/foundation/orbit/mask-black.png +0 -0
  44. data/assets/images/foundation/orbit/pause-black.png +0 -0
  45. data/assets/images/foundation/orbit/right-arrow-small.png +0 -0
  46. data/assets/images/foundation/orbit/right-arrow.png +0 -0
  47. data/assets/images/foundation/orbit/rotator-black.png +0 -0
  48. data/assets/images/foundation/orbit/timer-black.png +0 -0
  49. data/views/haml/index.haml +0 -43
@@ -0,0 +1,109 @@
1
+ # ~*~ encoding: utf-8 ~*~
2
+ module Aladdin
3
+
4
+ module Render
5
+
6
+ # Renders table problems marked up in JSON as HTML.
7
+ #
8
+ # The grid should be given as a 2-dimensional array that represents the
9
+ # table to be filled in. +"?"+ is a special token used in the grid to
10
+ # indicate cells that require student input.
11
+ #
12
+ # The answer should also be given as a 2-dimensional array. However, a
13
+ # dummy token may be used in cells that do not require student input to
14
+ # cut redundancy. In the example below, the +"-"+ token is used.
15
+ #
16
+ # @example
17
+ # {
18
+ # "format": "table",
19
+ # "question": "fill me in",
20
+ # "grid": [[0, "?", 2], [3, "?", 5]],
21
+ # "answer": [["-", 1, "-"], ["-", 4, "-"]
22
+ # }
23
+ class Table < Problem
24
+
25
+ # Name of template file for rendering table problems.
26
+ TEMPLATE = 'table.haml'
27
+
28
+ # Optional headings key.
29
+ HEADINGS = 'headings'
30
+
31
+ # Required grid key.
32
+ GRID = 'grid'
33
+
34
+ # Special token indicating that the cell should be filled in.
35
+ FILL_ME_IN = '?'
36
+
37
+ accessor HEADINGS, GRID
38
+
39
+ # Ensures that the +headings+ key exists.
40
+ def initialize(json)
41
+ json[HEADINGS] ||= nil
42
+ super
43
+ end
44
+
45
+ # Checks if the given json contains a valid table.
46
+ # @return [Boolean] true iff the json contains a valid table.
47
+ def valid?
48
+ super and
49
+ valid_grid? and
50
+ valid_answer?
51
+ end
52
+
53
+ # Gets the expected answer, in www-form-urlencoded format.
54
+ # @return [Hash] answers, as expected from student's form submission
55
+ def answer
56
+ return @answer unless @answer.nil?
57
+ @answer = encode_answer
58
+ end
59
+
60
+ # @return [Boolean] true iff the given cell is an input cell
61
+ def self.input?(cell)
62
+ cell == FILL_ME_IN
63
+ end
64
+
65
+ private
66
+
67
+ # Iterates through each cell in the provided grid to look for answers
68
+ # cells that require input and takes the answer from the answers array.
69
+ # For example, if the answer for a cell at [0][1] is 6, the returned
70
+ # hash will contain
71
+ #
72
+ # {'0' => {'1' => 6}}
73
+ #
74
+ # @return [Hash] answers
75
+ def encode_answer
76
+ encoded, ans = {}, @json[ANSWER]
77
+ grid.each_with_index do |row, i|
78
+ row.each_with_index do |cell, j|
79
+ next unless Table.input? cell
80
+ encoded[i.to_s] ||= {}
81
+ encoded[i.to_s][j.to_s] = serialize ans[i][j]
82
+ end
83
+ end
84
+ encoded
85
+ end
86
+
87
+ # @return [Boolean] true iff the json contains a valid grid.
88
+ def valid_grid?
89
+ @json.has_key? GRID and is_2d_array? grid
90
+ end
91
+
92
+ # @return [Boolean] true iff +answer+ is a valid 2D array and has the
93
+ # same number of rows as +grid+.
94
+ def valid_answer?
95
+ ans = @json[ANSWER]
96
+ is_2d_array? ans and ans.size == grid.size
97
+ end
98
+
99
+ # @return [Boolean] true iff +t+ is a 2-dimensional array.
100
+ def is_2d_array?(t)
101
+ t.is_a? Array and t.all? { |row| row.is_a? Array }
102
+ end
103
+
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+
@@ -0,0 +1,32 @@
1
+ # ~*~ encoding: utf-8 ~*~
2
+ module Aladdin
3
+
4
+ module Render
5
+
6
+ # Child classes should provide a +TEMPLATE+ string constant that contains
7
+ # the path to the HAML file.
8
+ class Template
9
+
10
+ # Renders the given problem using {#view}.
11
+ # @todo TODO should probably show some error message in the preview,
12
+ # so that the author doesn't have to read the logs.
13
+ def render(locals={})
14
+ view.render Object.new, locals
15
+ end
16
+
17
+ private
18
+
19
+ # Retrieves the +view+ singleton. If it is nil, initializes it from
20
+ # +self.class.TEMPLATE+.
21
+ # @return [Haml::Engine] haml engine
22
+ def view
23
+ return @view unless @view.nil?
24
+ file = File.join Aladdin::VIEWS[:haml], self.class::TEMPLATE
25
+ @view = Haml::Engine.new(File.read file)
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,92 @@
1
+ # ~*~ encoding: utf-8 ~*~
2
+ require 'securerandom'
3
+
4
+ module Aladdin
5
+
6
+ # Student submission.
7
+ #
8
+ # == DANGER DANGER DANGER ==
9
+ # The scratchspace code assumes that there is only one submission at a time,
10
+ # it's unsuitable for production use. It does not impose any security
11
+ # restrictions at all.
12
+ class Submission
13
+ include Aladdin::Mixin::Logger
14
+ include Aladdin::Mixin::WeakComparator
15
+
16
+ SCRATCHSPACE = '.__ss'
17
+
18
+ # Creates a new student submission.
19
+ # @param [String] id exercise ID
20
+ # @param [Type] type quiz or code
21
+ # @param [Hash] params form values
22
+ # @param [String] input student input
23
+ def initialize(id, type, params, input)
24
+ @id, @type, @params, @input = id, type, params, input
25
+ end
26
+
27
+ # Verifies the student's submission.
28
+ def verify
29
+ case @type
30
+ when Type::CODE then verify_code
31
+ when Type::QUIZ then verify_quiz
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # Verifies quiz answers by comparing the submitted answer against the
38
+ # answer in the solution file.
39
+ # @return [String] (json-encoded) true iff the submitted answer is correct.
40
+ def verify_quiz
41
+ id = @id.gsub File::SEPARATOR, '' # protect against directory attacks
42
+ solution = File.expand_path id + Aladdin::DATA_EXT, Aladdin::DATA_DIR
43
+ File.open(solution, 'rb') { |f| same? @params['answer'], Marshal.restore(f) }.to_json
44
+ rescue => e
45
+ logger.warn e.message
46
+ false.to_json
47
+ end
48
+
49
+ # Executes the verification script and returns the JSON-encoded results.
50
+ # @example
51
+ # ./verify --id=0 --input=path/to/student/input
52
+ def verify_code
53
+ scratchspace do
54
+ # FIXME: catch errors
55
+ filename = SecureRandom.uuid
56
+ IO.write(filename, @input)
57
+ bin = File.join '..', Aladdin.config['verify']['bin']
58
+ `#{bin} --id=#{@id} --input=#{filename}`
59
+ IO.read 'genie-results.json'
60
+ end
61
+ end
62
+
63
+ def scratchspace
64
+ enter_scratchspace
65
+ results = yield
66
+ exit_scratchspace
67
+ return results
68
+ end
69
+
70
+ def enter_scratchspace
71
+ # TODO: handle errors
72
+ Dir.mkdir SCRATCHSPACE
73
+ Dir.chdir SCRATCHSPACE
74
+ end
75
+
76
+ def exit_scratchspace
77
+ # TODO: handle errors
78
+ Dir.chdir '..'
79
+ FileUtils.rm_rf SCRATCHSPACE
80
+ end
81
+
82
+ # Submission Type, for use as enum.
83
+ module Type
84
+ # Quiz Type
85
+ QUIZ = 'quiz'
86
+ # Code Type
87
+ CODE = 'code'
88
+ end
89
+
90
+ end
91
+
92
+ end
@@ -1,4 +1,4 @@
1
1
  module Aladdin
2
2
  # Version number.
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.3"
4
4
  end
Binary file
data/skeleton/index.md ADDED
@@ -0,0 +1,3 @@
1
+ # Lesson 0
2
+
3
+ This is your first lesson! Hack away.
@@ -0,0 +1,5 @@
1
+ -# coding: UTF-8
2
+ %form{id: id}
3
+ %input.ex-raw{type: 'hidden', value: raw}
4
+ %input.ex-id{type: 'hidden', value: id}
5
+ %a.button.run{href: "#"} Run
@@ -0,0 +1,6 @@
1
+ -# coding: UTF-8
2
+ .block-image
3
+ = img
4
+ %p
5
+ %strong Figure #{index}:
6
+ = caption
@@ -1,3 +1,4 @@
1
+ -# coding: UTF-8
1
2
  !!! 5
2
3
 
3
4
  -# paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/
@@ -10,23 +11,52 @@
10
11
  %meta{charset: 'utf-8'}
11
12
  %meta{name: 'viewport', content: 'width=device-width'}
12
13
 
13
- %title Welcome to Foundation
14
+ %title= title
14
15
  %link{rel: 'stylesheet', href:'stylesheets/app.css'}
15
- %link{rel: 'stylesheet', href:'stylesheets/pygment.css'}
16
- %link{rel: 'stylesheet', href:'stylesheets/github.css'}
17
16
  %script{src: 'javascripts/foundation/modernizr.foundation.js'}
18
17
 
19
18
  %body
20
19
 
21
20
  .row
22
- .twelve.columns
23
- %h1 Genie Tutorial
24
- %hr/
25
21
 
26
- .row
27
- .twelve.columns
28
- =yield
22
+ %section{role: 'complementary'}
23
+ .row
24
+ .six.columns
25
+ %h1= title
26
+ %h4
27
+ <!-- TODO: user gravatar -->
28
+ %img{src: 'images/no_gravatar.gif', width: 20, height: 20, alt: 'John Doe'}
29
+ <!-- TOOD: user name -->
30
+ %small John Doe
31
+ %h4.subheader= description
32
+ %div.panel.meta
33
+ %h6.subheader Dec 18, 2012 12:00PM
34
+ %ul.inline-list
35
+ -categories.each do |category|
36
+ %li
37
+ %a{href: '#'}=category
38
+ .six.columns
39
+ %h1
40
+ %a.th.hero{href: '#'}
41
+ <!-- TODO: graphic from user's repo -->
42
+ %img{src: 'images/graphic.png'}
43
+
44
+ %hr/
45
+ %section{role: 'lesson'}
46
+ .row
47
+ .twelve.columns
48
+ !=yield # output is sanitized by the sanitize gem
49
+
50
+ %hr/
29
51
 
52
+ .row#footer
53
+ .ten.columns
54
+ .radius.progress.success.ten
55
+ %span.meter.eight 80%
56
+ .two.columns
57
+ %span <!-- TODO: score goes here -->
58
+
59
+ -# foundation
30
60
  %script{src: 'javascripts/foundation/jquery.js'}
31
61
  %script{src: 'javascripts/foundation/jquery.cookie.js'}
32
62
  %script{src: 'javascripts/foundation/jquery.event.move.js'}
@@ -36,16 +66,18 @@
36
66
  %script{src: 'javascripts/foundation/jquery.foundation.buttons.js'}
37
67
  %script{src: 'javascripts/foundation/jquery.foundation.clearing.js'}
38
68
  %script{src: 'javascripts/foundation/jquery.foundation.forms.js'}
39
- %script{src: 'javascripts/foundation/jquery.foundation.joyride.js'}
40
69
  %script{src: 'javascripts/foundation/jquery.foundation.magellan.js'}
41
70
  %script{src: 'javascripts/foundation/jquery.foundation.mediaQueryToggle.js'}
42
71
  %script{src: 'javascripts/foundation/jquery.foundation.navigation.js'}
43
- %script{src: 'javascripts/foundation/jquery.foundation.orbit.js'}
44
- %script{src: 'javascripts/foundation/jquery.foundation.reveal.js'}
45
72
  %script{src: 'javascripts/foundation/jquery.foundation.tabs.js'}
46
73
  %script{src: 'javascripts/foundation/jquery.foundation.tooltips.js'}
47
74
  %script{src: 'javascripts/foundation/jquery.foundation.topbar.js'}
48
75
  %script{src: 'javascripts/foundation/jquery.placeholder.js'}
76
+ %script{src: 'javascripts/foundation/app.js'}
77
+
78
+ -# mathjax
79
+ %script{src: 'https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'}
80
+ %script{src: 'javascripts/app.js'}
49
81
 
50
82
  :plain
51
83
  </html>
@@ -0,0 +1,18 @@
1
+ -# coding: UTF-8
2
+ %form.question{id: "question_#{id}"}
3
+ %fieldset
4
+
5
+ %legend Problem #{index}
6
+ %input.q-id{type: 'hidden', value: id}
7
+
8
+ %p= question
9
+
10
+ .row.collapse
11
+ .ten.mobile-three.columns
12
+ -options.each do |value, label|
13
+ %label{for: 'answer'}
14
+ %input{name: 'answer', type: 'radio', value: value}
15
+ = label
16
+ .two.mobile-one.columns
17
+ %a.button.submit.small.expand.postfix.radius{href: '#'} Submit
18
+
@@ -0,0 +1,5 @@
1
+ -# coding: UTF-8
2
+ %dl#sections.sub-nav{'data-magellan-expedition' => 'fixed'}
3
+ -sections.each_with_index do |section, i|
4
+ %dd{'data-magellan-arrival' => "section_#{i}"}
5
+ %a{href: "#section_#{i}"}= section
@@ -0,0 +1,14 @@
1
+ -# coding: UTF-8
2
+ %form.question{id: "question_#{id}"}
3
+ %fieldset
4
+
5
+ %legend Problem #{index}
6
+ %input.q-id{type: 'hidden', value: id}
7
+
8
+ %p= question
9
+
10
+ .row.collapse
11
+ .ten.mobile-three.columns
12
+ %input{name: 'answer', type: 'text'}
13
+ .two.mobile-one.columns
14
+ %a.button.small.submit.expand.postfix.radius{href: '#'} Submit
@@ -0,0 +1,30 @@
1
+ -# coding: UTF-8
2
+ %form.question{id: "question_#{id}"}
3
+ %fieldset
4
+
5
+ %legend Problem #{index}
6
+ %input.q-id{type: 'hidden', value: id}
7
+
8
+ %p= question
9
+
10
+ .row
11
+ .twelve.columns
12
+ %table
13
+ %thead
14
+ -if headings
15
+ %tr
16
+ -headings.each do |heading|
17
+ %th= heading
18
+
19
+ %tbody
20
+ -grid.each_with_index do |row, i|
21
+ %tr
22
+ -row.each_with_index do |cell, j|
23
+ %td
24
+ - if Aladdin::Render::Table.input? cell
25
+ %input{name: "answer[#{i}][#{j}]", type: 'text'}
26
+ - else
27
+ = cell
28
+ .row
29
+ .two.columns.mobile-one
30
+ %a.button.small.submit.radius{href: '#'} Submit
data/views/scss/app.scss CHANGED
@@ -1,47 +1,53 @@
1
- // You custom settings file to override Foundation defaults
2
1
  @import "settings";
3
-
4
- // Comment out this import if you are customizing you imports below
5
2
  @import "foundation";
6
3
 
7
- // ----------------------------------------
8
- // Import specific parts of Foundation by commenting the import "foundation"
9
- // and uncommenting what you want below. You must uncomment the following if customizing
10
-
11
- // @import "compass/css3";
12
- // @import "foundation/settings";
13
- // @import "foundation/functions/all";
14
-
15
- // Control which mixins you have access too
16
-
17
- // @import "foundation/mixins/clearfix";
18
- // @import "foundation/mixins/css-triangle";
19
- // @import "foundation/mixins/font-size";
20
-
21
- // Must include next two for semantic grid to work
22
-
23
- // @import "foundation/mixins/respond-to";
24
- // @import "foundation/mixins/semantic-grid";
25
-
26
- // @import "modular-scale";
27
- // @import "foundation/common/globals";
28
-
29
- // Must include the grid for any responsiveness
30
-
31
- // @import "foundation/components/grid";
32
-
33
- // Control which common styles get compiled
34
-
35
- // @import "foundation/common/typography";
36
- // @import "foundation/common/forms";
37
-
38
- // Control which components you get if customizing
39
-
40
- // @import "foundation/components/modules/buttons";
41
- // @import "foundation/components/modules/tabs";
42
- // @import "foundation/components/modules/ui";
43
- // @import "foundation/components/modules/topbar";
44
- // @import "foundation/components/modules/navbar";
45
- // @import "foundation/components/modules/orbit";
46
- // @import "foundation/components/modules/reveal";
47
- // @import "foundation/components/modules/offcanvas";
4
+ /* settings */
5
+ $borderWidth: 2px;
6
+
7
+ .highlight {
8
+ @import "pygment";
9
+ @import "github";
10
+ }
11
+
12
+ @import "mathjax";
13
+
14
+ body {
15
+ padding: 0 $columnGutter/2;
16
+ }
17
+
18
+ /* Meta panel */
19
+ div.meta.panel {
20
+ border-top: $borderWidth solid $darkEdge;
21
+ }
22
+
23
+ /* Lesson graphic */
24
+ a.hero img {
25
+ width: 100%;
26
+ }
27
+
28
+ /* Floating navigation bar */
29
+ #sections {
30
+ background: darken($white, 5%);
31
+ padding-top: 10px;
32
+ width: $rowWidth + 18;
33
+ border-bottom: $borderWidth solid $darkEdge;
34
+ z-index: 3;
35
+ }
36
+
37
+ /* Progress bar */
38
+ #footer .meter {
39
+ color: $white;
40
+ font-weight: 800;
41
+ text-align: right;
42
+ padding: 2px 2px 0 0;
43
+ }
44
+
45
+ /* Styling within lesson */
46
+
47
+ section[role='lesson'] .block-image {
48
+ text-align: center;
49
+ }
50
+
51
+ section[role='lesson'] ul {
52
+ margin-left: 17px;
53
+ }