aladdin 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +16 -10
- data/assets/favicon.ico +0 -0
- data/assets/images/graphic.png +0 -0
- data/assets/images/no_gravatar.gif +0 -0
- data/assets/javascripts/app.js +76 -0
- data/bin/aladdin +2 -17
- data/lib/aladdin.rb +58 -13
- data/lib/aladdin/app.rb +27 -7
- data/lib/aladdin/cli.rb +29 -0
- data/lib/aladdin/commands/new.rb +16 -0
- data/lib/aladdin/commands/server.rb +10 -0
- data/lib/aladdin/mixin/logger.rb +26 -0
- data/lib/aladdin/mixin/weak_comparator.rb +60 -0
- data/lib/aladdin/render/error.rb +20 -0
- data/lib/aladdin/render/image.rb +54 -0
- data/lib/aladdin/render/markdown.rb +111 -8
- data/lib/aladdin/render/multi.rb +40 -0
- data/lib/aladdin/render/navigation.rb +34 -0
- data/lib/aladdin/render/problem.rb +114 -0
- data/lib/aladdin/render/sanitize.rb +3 -1
- data/lib/aladdin/render/short.rb +30 -0
- data/lib/aladdin/render/table.rb +109 -0
- data/lib/aladdin/render/template.rb +32 -0
- data/lib/aladdin/submission.rb +92 -0
- data/lib/aladdin/version.rb +1 -1
- data/skeleton/images/graphic.png +0 -0
- data/skeleton/index.md +3 -0
- data/views/haml/exe.haml +5 -0
- data/views/haml/img.haml +6 -0
- data/views/haml/layout.haml +44 -12
- data/views/haml/multi.haml +18 -0
- data/views/haml/nav.haml +5 -0
- data/views/haml/short.haml +14 -0
- data/views/haml/table.haml +30 -0
- data/views/scss/app.scss +50 -44
- data/views/scss/mathjax.scss +5 -0
- data/views/scss/pygment.scss +9 -6
- metadata +47 -18
- data/assets/images/foundation/orbit/bullets.jpg +0 -0
- data/assets/images/foundation/orbit/left-arrow-small.png +0 -0
- data/assets/images/foundation/orbit/left-arrow.png +0 -0
- data/assets/images/foundation/orbit/loading.gif +0 -0
- data/assets/images/foundation/orbit/mask-black.png +0 -0
- data/assets/images/foundation/orbit/pause-black.png +0 -0
- data/assets/images/foundation/orbit/right-arrow-small.png +0 -0
- data/assets/images/foundation/orbit/right-arrow.png +0 -0
- data/assets/images/foundation/orbit/rotator-black.png +0 -0
- data/assets/images/foundation/orbit/timer-black.png +0 -0
- data/views/haml/index.haml +0 -43
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
[![Dependency Status](https://gemnasium.com/jimjh/aladdin.png)](https://gemnasium.com/jimjh/aladdin)
|
4
4
|
[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/jimjh/aladdin)
|
5
5
|
|
6
|
-
Generates
|
6
|
+
Generates lessons using the markdown document and tests provided by the author.
|
7
7
|
|
8
8
|
## Installation
|
9
9
|
Add this line to your application's Gemfile:
|
@@ -21,18 +21,24 @@ Or install it yourself as:
|
|
21
21
|
$> gem install aladdin
|
22
22
|
|
23
23
|
## Usage
|
24
|
-
Create a new directory for your notes _e.g._ `
|
25
|
-
directory,
|
26
|
-
like:
|
24
|
+
Create a new directory for your notes _e.g._ `lesson_0`. Change into that
|
25
|
+
directory, then execute:
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
03-the-end.md
|
27
|
+
```sh
|
28
|
+
$> aladdin new
|
29
|
+
```
|
32
30
|
|
33
|
-
Finally, execute aladdin to launch the Sinatra server:
|
31
|
+
Update `index.md` and provide your unit tests in the lesson directory. Finally, execute aladdin to launch the Sinatra server:
|
34
32
|
|
35
|
-
|
33
|
+
```sh
|
34
|
+
$> aladdin server
|
35
|
+
```
|
36
|
+
|
37
|
+
Note that the following directory names are reserved:
|
38
|
+
|
39
|
+
- javascripts
|
40
|
+
- stylesheets
|
41
|
+
- verify
|
36
42
|
|
37
43
|
## Contributing
|
38
44
|
|
data/assets/favicon.ico
ADDED
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,76 @@
|
|
1
|
+
/* ========================================================================
|
2
|
+
* app.js
|
3
|
+
* http://github.com/jimjh/aladdin
|
4
|
+
* ========================================================================
|
5
|
+
* Copyright (c) 2012 Carnegie Mellon University
|
6
|
+
* License: https://raw.github.com/jimjh/aladdin/master/LICENSE
|
7
|
+
* ========================================================================
|
8
|
+
*/
|
9
|
+
/*jshint strict:true unused:true*/
|
10
|
+
/*global $*/
|
11
|
+
|
12
|
+
;(function () {
|
13
|
+
'use strict';
|
14
|
+
|
15
|
+
// TODO: refactor
|
16
|
+
|
17
|
+
// Shows results of last submission at the given button and form.
|
18
|
+
var showResult = function(button, form) {
|
19
|
+
return function(result) {
|
20
|
+
switch(result) {
|
21
|
+
case true:
|
22
|
+
button.addClass('success');
|
23
|
+
form.removeClass('error');
|
24
|
+
break;
|
25
|
+
case false:
|
26
|
+
button.removeClass('success');
|
27
|
+
form.addClass('error');
|
28
|
+
break;
|
29
|
+
default:
|
30
|
+
$.each(result, function(i, row) {
|
31
|
+
$.each(row, function(j, cell) {
|
32
|
+
var input = form.find("input[name='answer["+i+"]["+j+"]']");
|
33
|
+
if (cell) input.addClass('success'); else input.addClass('error');
|
34
|
+
});
|
35
|
+
});
|
36
|
+
}
|
37
|
+
};
|
38
|
+
};
|
39
|
+
|
40
|
+
// Adds click listeners to the submit buttons.
|
41
|
+
var observeSubmitButton = function() {
|
42
|
+
|
43
|
+
$('a.button.submit').click(function(e) {
|
44
|
+
var button = $(e.target);
|
45
|
+
var form = button.parents('form');
|
46
|
+
var id = form.find('input.q-id').val();
|
47
|
+
$.post('/verify/quiz/' + id, form.serialize(), showResult(button, form));
|
48
|
+
return false;
|
49
|
+
});
|
50
|
+
|
51
|
+
};
|
52
|
+
|
53
|
+
// Adds click listeners to the run buttons.
|
54
|
+
// var run = function() {
|
55
|
+
// $('a.button.run').click(function(e){
|
56
|
+
// var form = $(e.target).parents('form');
|
57
|
+
// var raw = form.find('.ex-raw').val();
|
58
|
+
// var id = form.find('.ex-id').val();
|
59
|
+
// $.post('/verify/code/' + id, raw,
|
60
|
+
// function(data) { console.log(data); }
|
61
|
+
// );
|
62
|
+
// return false;
|
63
|
+
// });
|
64
|
+
// };
|
65
|
+
|
66
|
+
var genie = {
|
67
|
+
|
68
|
+
launch: function() {
|
69
|
+
observeSubmitButton();
|
70
|
+
}
|
71
|
+
|
72
|
+
};
|
73
|
+
|
74
|
+
$(genie.launch());
|
75
|
+
|
76
|
+
})();
|
data/bin/aladdin
CHANGED
@@ -1,20 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
$:.unshift File.join
|
3
|
+
$:.unshift File.join File.dirname(__FILE__), *%w(.. lib)
|
4
4
|
|
5
|
-
require 'aladdin'
|
6
|
-
require 'optparse'
|
7
|
-
|
8
|
-
opt_parser = OptionParser.new do |opts|
|
9
|
-
|
10
|
-
opts.banner = 'Usage: aladdin /path/to/tutorial/directory'
|
11
|
-
|
12
|
-
opts.on_tail('--version', 'Display current gem version.') do
|
13
|
-
puts 'aladdin v' + Aladdin::VERSION
|
14
|
-
exit 0
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
opt_parser.parse!
|
19
|
-
|
20
|
-
Aladdin.launch from: ARGV[0]
|
5
|
+
require 'aladdin/cli'
|
data/lib/aladdin.rb
CHANGED
@@ -4,27 +4,66 @@ require 'zurb-foundation'
|
|
4
4
|
require 'albino'
|
5
5
|
require 'haml'
|
6
6
|
require 'redcarpet'
|
7
|
+
require 'htmlentities'
|
7
8
|
require 'sanitize'
|
9
|
+
require 'yaml'
|
10
|
+
require 'json'
|
8
11
|
|
9
|
-
require 'aladdin/
|
12
|
+
require 'aladdin/mixin/logger'
|
13
|
+
require 'aladdin/mixin/weak_comparator'
|
14
|
+
require 'aladdin/submission'
|
10
15
|
require 'aladdin/render/markdown'
|
11
16
|
|
12
|
-
# Aladdin is
|
17
|
+
# Aladdin is a gem that tutorial authors can use to preview and test their
|
18
|
+
# tutorials locally.
|
13
19
|
module Aladdin
|
14
20
|
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
# Name of configuration file.
|
22
|
+
CONFIG_FILE = '.genie.yml'
|
23
|
+
|
24
|
+
# Default configuration options.
|
25
|
+
DEFAULT_CONFIG = {
|
26
|
+
'verify' => {
|
27
|
+
'bin' => 'make',
|
28
|
+
'arg_prefix' => ''
|
29
|
+
},
|
30
|
+
'title' => 'Lesson X',
|
31
|
+
'description' => 'This is a placeholder description. You should provide your own',
|
32
|
+
'categories' => []
|
33
|
+
}
|
34
|
+
|
35
|
+
class << self
|
36
|
+
|
37
|
+
attr_reader :config, :root
|
38
|
+
|
39
|
+
# Launches the tutorial app using 'thin' as the default webserver.
|
40
|
+
# @option opts [String] from path to author's markdown documents;
|
41
|
+
# defaults to the current working directory
|
42
|
+
def launch(opts = {})
|
43
|
+
@root = opts[:from] || '.'
|
44
|
+
configure
|
45
|
+
Aladdin::App.set :views, Aladdin::VIEWS.merge(markdown: root)
|
46
|
+
Aladdin::App.run!
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Reads configuration options from +.genie.yml+ and merges it into
|
52
|
+
# {DEFAULT_CONFIG}.
|
53
|
+
def configure
|
54
|
+
config_file = File.expand_path CONFIG_FILE, root
|
55
|
+
config = File.exists?(config_file) ? YAML.load_file(config_file) : {}
|
56
|
+
@config = DEFAULT_CONFIG.merge(config) { |k, l, r|
|
57
|
+
(l.is_a?(Hash) and r.is_a?(Hash)) ? l.merge(r) : r
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
# Converts a hash to struct.
|
62
|
+
def to_struct(hash)
|
63
|
+
Struct.new( *(k = hash.keys) ).new( *hash.values_at( *k ) )
|
64
|
+
end
|
22
65
|
|
23
|
-
# Converts a hash to struct.
|
24
|
-
def self.to_struct(hash)
|
25
|
-
Struct.new( *(k = hash.keys) ).new( *hash.values_at( *k ) )
|
26
66
|
end
|
27
|
-
private_class_method :to_struct
|
28
67
|
|
29
68
|
# Paths to different types of views.
|
30
69
|
VIEWS = {
|
@@ -38,6 +77,12 @@ module Aladdin
|
|
38
77
|
assets: File.expand_path('../../assets', __FILE__),
|
39
78
|
).freeze
|
40
79
|
|
80
|
+
# File extension for solution files.
|
81
|
+
DATA_EXT = '.sol'
|
82
|
+
|
83
|
+
# @todo TODO allow configuration?
|
84
|
+
DATA_DIR = Dir.tmpdir
|
85
|
+
|
41
86
|
end
|
42
87
|
|
43
88
|
require 'aladdin/app'
|
data/lib/aladdin/app.rb
CHANGED
@@ -7,15 +7,19 @@ module Aladdin
|
|
7
7
|
# Adapted from https://github.com/jerodsanto/sinatra-foundation-skeleton/
|
8
8
|
class App < Sinatra::Base
|
9
9
|
|
10
|
+
# Default page
|
11
|
+
INDEX = :index
|
12
|
+
|
10
13
|
# Default markdown options.
|
11
14
|
MARKDOWN_OPTIONS = {
|
12
15
|
renderer: Aladdin::Render::HTML,
|
16
|
+
layout_engine: :haml,
|
13
17
|
no_intra_emphasis: true,
|
14
18
|
tables: true,
|
15
19
|
fenced_code_blocks: true,
|
16
20
|
autolink: true,
|
17
21
|
strikethrough: true,
|
18
|
-
|
22
|
+
tables: true,
|
19
23
|
}
|
20
24
|
|
21
25
|
class << self
|
@@ -40,7 +44,7 @@ module Aladdin
|
|
40
44
|
set :public_folder, Aladdin::PATHS.assets
|
41
45
|
end
|
42
46
|
|
43
|
-
# Configures ZURB's compass to compile
|
47
|
+
# Configures ZURB's compass to compile aladdin's scss assets.
|
44
48
|
# @return [void]
|
45
49
|
def configure_compass
|
46
50
|
Compass.configuration do |config|
|
@@ -50,12 +54,12 @@ module Aladdin
|
|
50
54
|
set :scss, Compass.sass_engine_options
|
51
55
|
end
|
52
56
|
|
53
|
-
# Registers redcarpet2 and
|
54
|
-
# the github-flavored markdown as possible.
|
57
|
+
# Registers redcarpet2 and configures aladdin's markdown renderer.
|
55
58
|
# @return [void]
|
56
59
|
def configure_markdown
|
57
|
-
Tilt.register Tilt::RedcarpetTemplate::Redcarpet2,
|
60
|
+
Tilt.register Tilt::RedcarpetTemplate::Redcarpet2, *%w(markdown mkd md)
|
58
61
|
set :markdown, MARKDOWN_OPTIONS
|
62
|
+
set :haml, escape_html: true
|
59
63
|
end
|
60
64
|
|
61
65
|
end
|
@@ -64,10 +68,13 @@ module Aladdin
|
|
64
68
|
# @param block block to call within wrapper
|
65
69
|
def render_or_pass(&block)
|
66
70
|
begin return block.call
|
67
|
-
rescue
|
71
|
+
rescue Exception => e
|
72
|
+
logger.error e.message
|
73
|
+
pass
|
68
74
|
end
|
69
75
|
end
|
70
76
|
|
77
|
+
enable :logging
|
71
78
|
configure_views
|
72
79
|
configure_markdown
|
73
80
|
|
@@ -80,8 +87,21 @@ module Aladdin
|
|
80
87
|
render_or_pass { scss path.to_sym }
|
81
88
|
end
|
82
89
|
|
90
|
+
get '/img/*' do |path|
|
91
|
+
send_file File.join('img', path)
|
92
|
+
end
|
93
|
+
|
83
94
|
get '/*' do |path|
|
84
|
-
|
95
|
+
path = path.empty? ? INDEX : path.to_sym
|
96
|
+
render_or_pass do
|
97
|
+
markdown(path, locals: Aladdin.config)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
post '/verify/:type/:id' do
|
102
|
+
input = request.body.read
|
103
|
+
content_type :json
|
104
|
+
Submission.new(params[:id], params[:type], params, input).verify
|
85
105
|
end
|
86
106
|
|
87
107
|
end
|
data/lib/aladdin/cli.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
|
3
|
+
Signal.trap('INT') { puts; exit(1) }
|
4
|
+
|
5
|
+
case ARGV.first
|
6
|
+
when '--version', '-v'
|
7
|
+
puts "Aladdin #{Aladdin::VERSION}"
|
8
|
+
exit(0)
|
9
|
+
when 'new'
|
10
|
+
ARGV.shift
|
11
|
+
require_relative 'commands/new'
|
12
|
+
when 'server'
|
13
|
+
ARGV.shift
|
14
|
+
require_relative 'commands/server'
|
15
|
+
else
|
16
|
+
puts <<-eos
|
17
|
+
Usage:
|
18
|
+
aladdin COMMAND [options]
|
19
|
+
|
20
|
+
Commands:
|
21
|
+
new # generates the skeleton for a new lesson
|
22
|
+
server # launches a preview server
|
23
|
+
|
24
|
+
Aladdin Options:
|
25
|
+
-v, [--version] # show version number and quit
|
26
|
+
-h, [--help] # show this help message and quit
|
27
|
+
eos
|
28
|
+
exit ['-h', '--help'].include?(ARGV.first) ? 0 : 1
|
29
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
require 'aladdin'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
# Array of skeleton files to be copied over.
|
6
|
+
SKELETON_FILES = %w(.genie.yml index.md images)
|
7
|
+
|
8
|
+
opt_parser = OptionParser.new do |opts|
|
9
|
+
opts.banner = "Usage: aladdin new [options] [LESSON_PATH]"
|
10
|
+
end
|
11
|
+
opt_parser.parse!
|
12
|
+
|
13
|
+
root = ARGV[0] || Dir.pwd
|
14
|
+
|
15
|
+
Dir.chdir File.join File.dirname(__FILE__), *%w(.. .. .. skeleton)
|
16
|
+
FileUtils.cp_r SKELETON_FILES, root, verbose: true
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Aladdin
|
5
|
+
|
6
|
+
# aladdin-mixin module contains all other mixin modules.
|
7
|
+
module Mixin
|
8
|
+
|
9
|
+
# @example
|
10
|
+
# require 'logger'
|
11
|
+
# logger.info "hey"
|
12
|
+
module Logger
|
13
|
+
|
14
|
+
# Global Logger.
|
15
|
+
LOGGER = ::Logger.new STDOUT
|
16
|
+
|
17
|
+
# Retrieves the global logger.
|
18
|
+
def logger
|
19
|
+
Logger::LOGGER
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
module Aladdin
|
3
|
+
|
4
|
+
module Mixin
|
5
|
+
|
6
|
+
# Provides a richer comparison with weak-typing.
|
7
|
+
# @see #same?
|
8
|
+
module WeakComparator
|
9
|
+
|
10
|
+
# Compares +submitted+ against +saved+ and returns a simple diff. Assumes
|
11
|
+
# that:
|
12
|
+
# - +saved+ is a ruby-marshalled value that carries rich type information
|
13
|
+
# - +submitted+ is a user-submitted value that is either a string or an
|
14
|
+
# hash
|
15
|
+
#
|
16
|
+
# === Booleans
|
17
|
+
# If +saved+ is +true+, then it will accept any +submitted+ value that
|
18
|
+
# begins with 'T' or 't'. Similarly, if +saved+ is +false+, then it will
|
19
|
+
# accept any +submitted+ value that begins with 'F' or 'f'.
|
20
|
+
#
|
21
|
+
# === Numerics
|
22
|
+
# If +saved+ is a numeric, then it will accept any +submitted+ value that
|
23
|
+
# is numerically equivalent to +saved+. For example, if +saved+ is 0,
|
24
|
+
# then +'0.0'+ and +'0'+ will both be accepted.
|
25
|
+
#
|
26
|
+
# @param [String, Hash] submitted
|
27
|
+
# @param [String, Numeric, Boolean, Hash] saved
|
28
|
+
# @return [Boolean, Hash] diff
|
29
|
+
def same?(submitted, saved)
|
30
|
+
case saved
|
31
|
+
when Hash
|
32
|
+
Hash === submitted and same_hash? submitted, saved
|
33
|
+
when String then saved == submitted
|
34
|
+
when Numeric then saved == JSON.parse(%|[#{submitted}]|).first
|
35
|
+
when TrueClass, FalseClass then saved.to_s[0] == submitted.downcase[0]
|
36
|
+
else false end
|
37
|
+
rescue
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Compares two hash and returns a simple diff. It checks that both
|
44
|
+
# +submitted+ and +saved+ have the same number of elements, and then
|
45
|
+
# compares each pair of elements in order.
|
46
|
+
#
|
47
|
+
# @param [Hash] submitted
|
48
|
+
# @param [Hash] saved
|
49
|
+
# @return [Boolean, Hash] diff
|
50
|
+
# @see #same?
|
51
|
+
def same_hash?(submitted, saved)
|
52
|
+
saved.count == submitted.count and
|
53
|
+
Hash[saved.map { |key, value| [key, same?(submitted[key], value)] }]
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|