aladdin 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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
+ }