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
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 tutorials using the set of markdown documents provided by the author.
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._ `my_tutorial`. Change into that
25
- directory, and create your notes using GitHub-Flavored Markdown. It might look
26
- like:
24
+ Create a new directory for your notes _e.g._ `lesson_0`. Change into that
25
+ directory, then execute:
27
26
 
28
- my_tutorial/
29
- 01-introduction.md
30
- 02-the-beginning.md
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
- $> aladdin
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
 
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(File.dirname(__FILE__), '..', 'lib')
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/render/sanitize'
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 for tutorial apps.
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
- # Launches the tutorial app using 'thin' as the default webserver.
16
- # @option opts [String] from path to author's markdown documents;
17
- # defaults to the current working directory
18
- def self.launch(opts = {})
19
- Aladdin::App.set :views, Aladdin::VIEWS.merge(markdown: opts[:from] || '.')
20
- Aladdin::App.run!
21
- end
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
- layout_engine: :haml
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 laddin's scss assets.
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 laddin's markdown renderer to be as close to
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, 'markdown', 'mkd', 'md'
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 pass
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
- render_or_pass { markdown path.to_sym }
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
@@ -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,10 @@
1
+ # ~*~ encoding: utf-8 ~*~
2
+ require 'aladdin'
3
+ require 'optparse'
4
+
5
+ opt_parser = OptionParser.new do |opts|
6
+ opts.banner = "Usage: aladdin server [options] [LESSON_PATH]"
7
+ end
8
+ opt_parser.parse!
9
+
10
+ Aladdin.launch from: ARGV[0]
@@ -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