mango 0.1.1 → 0.5.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.yardopts +6 -0
- data/README.mdown +62 -29
- data/Rakefile +62 -0
- data/VERSION +1 -0
- data/bin/mango +5 -0
- data/doc/HISTORY.mdown +71 -0
- data/doc/ROAD-MAP.mdown +10 -0
- data/lib/mango.rb +7 -0
- data/lib/mango/application.rb +334 -0
- data/lib/mango/content_page.rb +193 -0
- data/lib/mango/dependencies.rb +125 -0
- data/lib/mango/flavored_markdown.rb +82 -0
- data/lib/mango/rack/debugger.rb +22 -0
- data/lib/mango/runner.rb +95 -0
- data/lib/mango/templates/Gemfile +4 -0
- data/lib/mango/templates/README.md +1 -0
- data/lib/mango/templates/config.ru +4 -0
- data/lib/mango/templates/content/index.md +5 -0
- data/lib/mango/templates/themes/default/public/images/particles.gif +0 -0
- data/lib/mango/templates/themes/default/public/javascripts/fireworks.js +546 -0
- data/lib/mango/templates/themes/default/public/javascripts/timer.js +5 -0
- data/lib/mango/templates/themes/default/public/robots.txt +5 -0
- data/lib/mango/templates/themes/default/public/styles/fireworks.css +55 -0
- data/lib/mango/templates/themes/default/public/styles/reset.css +54 -0
- data/lib/mango/templates/themes/default/styles/screen.sass +17 -0
- data/lib/mango/templates/themes/default/views/404.haml +21 -0
- data/lib/mango/templates/themes/default/views/layout.haml +20 -0
- data/lib/mango/templates/themes/default/views/page.haml +3 -0
- data/lib/mango/version.rb +6 -0
- data/mango.gemspec +35 -0
- data/spec/app_root/content/about/index.haml +1 -0
- data/spec/app_root/content/about/us.haml +1 -0
- data/spec/app_root/content/engines/haml.haml +7 -0
- data/spec/app_root/content/engines/markdown.markdown +7 -0
- data/spec/app_root/content/engines/md.md +7 -0
- data/spec/app_root/content/engines/mdown.mdown +7 -0
- data/spec/app_root/content/index.haml +1 -0
- data/spec/app_root/content/override.haml +1 -0
- data/spec/app_root/content/page_with_missing_view.haml +4 -0
- data/spec/app_root/content/turner+hooch.haml +1 -0
- data/spec/app_root/security_hole.haml +1 -0
- data/spec/app_root/themes/default/public/default.css +3 -0
- data/spec/app_root/themes/default/public/images/index.html +10 -0
- data/spec/app_root/themes/default/public/images/ripe-mango.jpg +0 -0
- data/spec/app_root/themes/default/public/override +10 -0
- data/spec/app_root/themes/default/public/robots.txt +2 -0
- data/spec/app_root/themes/default/public/styles/override.css +3 -0
- data/spec/app_root/themes/default/public/styles/reset.css +27 -0
- data/spec/app_root/themes/default/public/styles/subfolder/another.css +3 -0
- data/spec/app_root/themes/default/security_hole.sass +1 -0
- data/spec/app_root/themes/default/security_hole.txt +1 -0
- data/spec/app_root/themes/default/styles/override.sass +2 -0
- data/spec/app_root/themes/default/styles/screen.sass +13 -0
- data/spec/app_root/themes/default/styles/subfolder/screen.sass +12 -0
- data/spec/app_root/themes/default/views/404.haml +7 -0
- data/spec/app_root/themes/default/views/layout.haml +7 -0
- data/spec/app_root/themes/default/views/page.haml +4 -0
- data/spec/mango/application/routing_content_pages_spec.rb +357 -0
- data/spec/mango/application/routing_public_files_spec.rb +181 -0
- data/spec/mango/application/routing_style_sheets_spec.rb +286 -0
- data/spec/mango/application_spec.rb +34 -0
- data/spec/mango/content_page/finding_spec.rb +213 -0
- data/spec/mango/content_page/initializing_spec.rb +298 -0
- data/spec/mango/content_page_spec.rb +44 -0
- data/spec/mango/dependencies_spec.rb +189 -0
- data/spec/mango/flavored_markdown_spec.rb +52 -0
- data/spec/mango/rack/debugger_spec.rb +114 -0
- data/spec/mango/version_spec.rb +18 -0
- data/spec/quality_spec.rb +32 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/matchers/malformed_whitespace_matchers.rb +60 -0
- metadata +304 -17
@@ -0,0 +1,193 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "bluecloth"
|
4
|
+
require "haml"
|
5
|
+
require "yaml"
|
6
|
+
|
7
|
+
module Mango
|
8
|
+
# `ContentPage` is a **model** class. An instance of `ContentPage` is the representation of a
|
9
|
+
# single content file. The primary responsiblity of `ContentPage` is to manage the conversion of
|
10
|
+
# user-generated data into HTML. It accomplishes this task by utilizing 3rd-party content
|
11
|
+
# engines, which convert easy-to-read, easy-to-write plain text and markup into structurally
|
12
|
+
# valid HTML.
|
13
|
+
#
|
14
|
+
# `ContentPage` instances have two primary components -- **a body** and some **attributes**.
|
15
|
+
# Each component is defined within a single content file.
|
16
|
+
#
|
17
|
+
# ### Example content file
|
18
|
+
#
|
19
|
+
# # mango_poem.markdown
|
20
|
+
# ---
|
21
|
+
# title: The Sin of Mango
|
22
|
+
# categories:
|
23
|
+
# - bad
|
24
|
+
# - poetry
|
25
|
+
# ---
|
26
|
+
# Mango is like a drug.
|
27
|
+
# You must have more and more and more of the Mango
|
28
|
+
# until there is no Mango left.
|
29
|
+
# Not even for Mango!
|
30
|
+
#
|
31
|
+
# Mangos aside, let's bring attention to a few important facets of this example and content files
|
32
|
+
# in general.
|
33
|
+
#
|
34
|
+
# 1. Content pages are stored as files on disk. Here, the file name is `mango_poem.markdown`.
|
35
|
+
# 2. Attributes are defined first, embedded within triple-dashed ("---") dividers.
|
36
|
+
# 3. The body comes second, nestled comfortably below the attributes header.
|
37
|
+
# 4. Attributes are key-value pairs, defined with [YAML](http://www.yaml.org/) formatting.
|
38
|
+
# 5. The body, in this example, is plain-text. Because of the file extension, it's interpretted
|
39
|
+
# as Markdown.
|
40
|
+
#
|
41
|
+
# ### The Body
|
42
|
+
#
|
43
|
+
# The body of a content file may be written using one of the following human-friendly formats:
|
44
|
+
#
|
45
|
+
# * [Markdown](http://daringfireball.net/projects/markdown/syntax) extended with
|
46
|
+
# `Mango::FlavoredMarkdown`
|
47
|
+
# * [Haml](http://haml-lang.com/)
|
48
|
+
#
|
49
|
+
# The content file's extension determines the body's formatting. For a complete list of content
|
50
|
+
# file formats and their extensions, see `Mango::ContentPage::CONTENT_ENGINES`
|
51
|
+
#
|
52
|
+
# `ContentPage` instances are expected to be passed along into the view template they define.
|
53
|
+
# Once in that scope, the instance can convert its body to HTML with the `#to_html` method:
|
54
|
+
#
|
55
|
+
# @content_page.to_html
|
56
|
+
#
|
57
|
+
# ### The Attributes
|
58
|
+
#
|
59
|
+
# Attributes are key-value pairs, defined with [YAML](http://www.yaml.org/) formatting.
|
60
|
+
#
|
61
|
+
# All `ContentPage` instances have a `view` attribute, even if one is not explicitly declared in
|
62
|
+
# the content file. This attribute is essential as it guides the `Mango::Application` to render
|
63
|
+
# the correct view template file.
|
64
|
+
#
|
65
|
+
# When declaring an explicit view template, only the base file name is important. **It's assumed
|
66
|
+
# that all view templates are in Haml format.** The default view template file name is defined
|
67
|
+
# by `Mango::ContentPage::DEFAULT["attributes"]`.
|
68
|
+
#
|
69
|
+
# Syntactic sugar has been added for accessing attribtues. For example:
|
70
|
+
#
|
71
|
+
# @content_page.attributes["title"]
|
72
|
+
#
|
73
|
+
# can be shortened to
|
74
|
+
#
|
75
|
+
# @content_page.title
|
76
|
+
#
|
77
|
+
# Again, `ContentPage` instances are expected to be passed along into the view template they
|
78
|
+
# define. With a `@content_page` instance in scope, accessing attributes inside a Haml template
|
79
|
+
# works like this:
|
80
|
+
#
|
81
|
+
# %title
|
82
|
+
# = @content_page.title
|
83
|
+
#
|
84
|
+
# @see Mango::FlavoredMarkdown
|
85
|
+
#
|
86
|
+
class ContentPage
|
87
|
+
class PageNotFound < RuntimeError; end
|
88
|
+
|
89
|
+
# Known content formats and their associated file extensions
|
90
|
+
CONTENT_ENGINES = {
|
91
|
+
:markdown => ["md", "mdown", "markdown"],
|
92
|
+
:haml => ["haml"]
|
93
|
+
}
|
94
|
+
|
95
|
+
# Default values for various accessors
|
96
|
+
DEFAULT = {
|
97
|
+
:attributes => { "view" => :page },
|
98
|
+
:body => "",
|
99
|
+
:content_engine => :markdown
|
100
|
+
}
|
101
|
+
|
102
|
+
# `String`
|
103
|
+
attr_reader :data
|
104
|
+
# `Hash`
|
105
|
+
attr_reader :attributes
|
106
|
+
# `String`
|
107
|
+
attr_reader :body
|
108
|
+
# `Symbol`
|
109
|
+
attr_reader :content_engine
|
110
|
+
|
111
|
+
# Creates a new instance by extracting the body and attributes from raw data. Any extracted
|
112
|
+
# components found are merged with their defaults.
|
113
|
+
#
|
114
|
+
# @param [String] data
|
115
|
+
# @param [Hash] options
|
116
|
+
# @option options [Symbol] :content_engine See `CONTENT_ENGINES` and `DEFAULT[:content_engine]`
|
117
|
+
#
|
118
|
+
def initialize(data, options = {})
|
119
|
+
@data = data
|
120
|
+
@content_engine = options.delete(:content_engine) || DEFAULT[:content_engine]
|
121
|
+
|
122
|
+
if self.data =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
123
|
+
@attributes = DEFAULT[:attributes].merge(YAML.load($1) || {})
|
124
|
+
@body = self.data[($1.size + $2.size)..-1]
|
125
|
+
else
|
126
|
+
@attributes = DEFAULT[:attributes]
|
127
|
+
@body = self.data || DEFAULT[:body]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Create a new instance by searching for and reading a content file from disk. Content files
|
132
|
+
# are searched consecutively until a page with known content extension is found.
|
133
|
+
#
|
134
|
+
# @param [String] path_without_extension
|
135
|
+
# @raise [PageNotFound] Raised when a content page cannot be found
|
136
|
+
# @return [ContentPage] A new instance is created and returned when found
|
137
|
+
#
|
138
|
+
def self.find_by_path(path_without_extension)
|
139
|
+
CONTENT_ENGINES.each_pair do |content_engine, extensions|
|
140
|
+
extensions.each do |extension|
|
141
|
+
path = "#{path_without_extension}.#{extension}"
|
142
|
+
return new(File.read(path), :content_engine => content_engine) if File.exist?(path)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
raise PageNotFound, "Unable to find content page for path -- #{path_without_extension}"
|
147
|
+
end
|
148
|
+
|
149
|
+
# Given a content engine, converts the body to HTML.
|
150
|
+
#
|
151
|
+
# @raise [RuntimeError] Raised when content engine is unknown
|
152
|
+
# @return [String] HTML from the conversion
|
153
|
+
#
|
154
|
+
def to_html
|
155
|
+
case content_engine
|
156
|
+
when :markdown
|
157
|
+
BlueCloth.new(Mango::FlavoredMarkdown.shake(body)).to_html
|
158
|
+
when :haml
|
159
|
+
Haml::Engine.new(body).to_html
|
160
|
+
else
|
161
|
+
raise "Unknown content engine -- #{content_engine}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Returns just the view template's base file name. It's assumed that all view templates are in
|
166
|
+
# Haml format.
|
167
|
+
#
|
168
|
+
# @example
|
169
|
+
# @content_page.attributes["view"] #=> "blog.haml"
|
170
|
+
# @content_page.view #=> :blog
|
171
|
+
#
|
172
|
+
# @return [Symbol] The view template's base file name.
|
173
|
+
#
|
174
|
+
def view
|
175
|
+
File.basename(attributes["view"], '.*').to_sym
|
176
|
+
end
|
177
|
+
|
178
|
+
# Adds syntactic suger for reading attributes.
|
179
|
+
#
|
180
|
+
# @example
|
181
|
+
# @content_page.title == @content_page.attributes["title"]
|
182
|
+
#
|
183
|
+
# @param [Symbol] method_name
|
184
|
+
# @raise [NoMethodError] Raised when there is no method name key in attributes
|
185
|
+
# @return [Object] Value of the method name attribute
|
186
|
+
#
|
187
|
+
def method_missing(method_name)
|
188
|
+
key = method_name.to_s
|
189
|
+
attributes.has_key?(key) ? attributes[key] : super
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Mango
|
4
|
+
# `Mango::Dependencies` is a class-methods-only **singleton** class that performs both strict
|
5
|
+
# parse-time dependency checking and simple, elegant run-time dependency warning.
|
6
|
+
#
|
7
|
+
# My preferred mental model is to consider software library dependencies as a snapshot in time.
|
8
|
+
# I also make the assumption, sometimes incorrectly, that a newer version of a software library
|
9
|
+
# is not always a better version.
|
10
|
+
#
|
11
|
+
# `Mango::Dependencies` automatically enforces a strict parse-time check for the
|
12
|
+
# `REQUIRED_RUBY_VERSION` on both application and development processes for the `Mango` library.
|
13
|
+
# (i.e. `bin/mango`, `rake`, `spec`, etc) Because of this, I've ensured this file is
|
14
|
+
# syntactically compatible with Ruby 1.8.6 or higher.
|
15
|
+
#
|
16
|
+
# Currently, `Mango` does **not** enforce strict parse-time version checking on `DEVELOPMENT_GEMS`.
|
17
|
+
# In the future, I would like to experiment with using RubyGems and the `Kernel#gem` method to
|
18
|
+
# this end. For now, each developer is responsible for ensuring the correct versions of their
|
19
|
+
# necessary development gems are located in the `$LOAD_PATH` on their system.
|
20
|
+
#
|
21
|
+
# When a gem is required, but a `LoadError` is raised, and rescued, `Mango::Dependencies` can be
|
22
|
+
# incorporated into the process to warn a developer of missing development features. Even with a
|
23
|
+
# few methods -- `.create_warning_for` and `.warn_at_exit` -- users are automatically warned, in
|
24
|
+
# this case at the moment of termination, about gems that could not found be in the `$LOAD_PATH`.
|
25
|
+
# Using `Mango::Dependencies` is **not** a mandatory inclusion for all gem requirements, merely a
|
26
|
+
# guide to help developers quickly see obstacles in their path.
|
27
|
+
#
|
28
|
+
# @example Simple usage with the YARD gem
|
29
|
+
# begin
|
30
|
+
# require "yard"
|
31
|
+
# YARD::Rake::YardocTask.new(:yard)
|
32
|
+
# rescue LoadError => e
|
33
|
+
# Mango::Dependencies.create_warning_for(e)
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# @see Mango::Dependencies.create_warning_for
|
37
|
+
# @see Mango::Dependencies.warn_at_exit
|
38
|
+
class Dependencies
|
39
|
+
# For now, starting with Ruby 1.9.1 but I would like to experiment with compatibility with Ruby >= 1.9.1 in the future.
|
40
|
+
REQUIRED_RUBY_VERSION = "1.9.1"
|
41
|
+
|
42
|
+
# bluecloth is a hidden yard dependency for markdown support
|
43
|
+
DEVELOPMENT_GEMS = {
|
44
|
+
:"rack-test" => "0.5.4",
|
45
|
+
:rspec => "1.3.0",
|
46
|
+
:yard => "0.5.8",
|
47
|
+
:"yard-sinatra" => "0.5.0",
|
48
|
+
:bluecloth => "2.0.7"
|
49
|
+
}
|
50
|
+
|
51
|
+
FILE_NAME_TO_GEM_NAME = {
|
52
|
+
:"rack/test" => :"rack-test",
|
53
|
+
:"spec/rake/spectask" => :rspec,
|
54
|
+
:"yard/sinatra" => :"yard-sinatra"
|
55
|
+
}
|
56
|
+
|
57
|
+
# Empties the warnings cache. This method is called when the class is required.
|
58
|
+
def self.destroy_warnings
|
59
|
+
@@warnings_cache = []
|
60
|
+
end
|
61
|
+
destroy_warnings
|
62
|
+
|
63
|
+
# Creates and caches a warning from a `LoadError` exception. Warnings are only created for
|
64
|
+
# known development gem dependencies.
|
65
|
+
#
|
66
|
+
# @param [LoadError] error A rescued exception
|
67
|
+
# @raise [RuntimeError] Raised when the `LoadError` argument is an unknown development gem.
|
68
|
+
def self.create_warning_for(error)
|
69
|
+
pattern = %r{no such file to load -- ([\w\-\\/]*)}
|
70
|
+
error.message.match(pattern) do |match_data|
|
71
|
+
file_name = match_data[1].to_sym
|
72
|
+
gem_name = if DEVELOPMENT_GEMS.has_key?(file_name)
|
73
|
+
file_name
|
74
|
+
elsif FILE_NAME_TO_GEM_NAME.has_key?(file_name)
|
75
|
+
FILE_NAME_TO_GEM_NAME[file_name]
|
76
|
+
else
|
77
|
+
raise "Cannot create a dependency warning for unknown development gem -- #{file_name}"
|
78
|
+
end
|
79
|
+
|
80
|
+
@@warnings_cache << "#{gem_name} --version '#{DEVELOPMENT_GEMS[gem_name]}'"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Displays a warning message to the user on the standard output channel if there are warnings
|
85
|
+
# to render.
|
86
|
+
#
|
87
|
+
# @example Sample warning message
|
88
|
+
# The following development gem dependencies could not be found. Without them, some available development features are missing:
|
89
|
+
# jeweler --version "1.4.0"
|
90
|
+
# rspec --version "1.3.0"
|
91
|
+
# yard --version "0.5.3"
|
92
|
+
# bluecloth --version "2.0.7"
|
93
|
+
def self.render_warnings
|
94
|
+
unless @@warnings_cache.empty?
|
95
|
+
message = []
|
96
|
+
message << "The following development gem dependencies could not be found. Without them, some available development features are missing:"
|
97
|
+
message += @@warnings_cache
|
98
|
+
puts "\n" + message.join("\n")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Attaches a call to `render_warnings` to `Kernel#at_exit`
|
103
|
+
def self.warn_at_exit
|
104
|
+
at_exit { render_warnings }
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
# Checks that the version of the current Ruby process matches the `REQUIRED_RUBY_VERSION`.
|
110
|
+
# This method is automatically invoked at the first time this class is required, ensuring the
|
111
|
+
# correct Ruby version at parse-time.
|
112
|
+
#
|
113
|
+
# @param [String] ruby_version Useful for automated specifications. Defaults to `RUBY_VERSION`.
|
114
|
+
# @raise [SystemExit] Raised, with a message, when the process is using an incorrect version of Ruby.
|
115
|
+
def self.check_ruby_version(ruby_version = RUBY_VERSION)
|
116
|
+
unless ruby_version == REQUIRED_RUBY_VERSION
|
117
|
+
abort <<-ERROR
|
118
|
+
This library requires Ruby #{REQUIRED_RUBY_VERSION}, but you're using #{ruby_version}.
|
119
|
+
Please visit http://www.ruby-lang.org/ for installation instructions.
|
120
|
+
ERROR
|
121
|
+
end
|
122
|
+
end
|
123
|
+
check_ruby_version
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "digest/md5"
|
2
|
+
|
3
|
+
module Mango
|
4
|
+
# `Mango::FlavoredMarkdown` (MFM) is a subset of GithubFlavoredMarkdown (GFM). MFM adds a touch
|
5
|
+
# seasoning to the standard Markdown (SM) syntax.
|
6
|
+
#
|
7
|
+
# ## Differences from standard Markdown
|
8
|
+
#
|
9
|
+
# ### Newlines
|
10
|
+
#
|
11
|
+
# The biggest difference that MFM introduces is in the handling of linebreaks. With SM you can
|
12
|
+
# hard wrap paragraphs of text and they will be combined into a single paragraph. I find this to
|
13
|
+
# be the cause of a huge number of unintentional formatting errors. MFM treats newlines in
|
14
|
+
# paragraph-like content as real line breaks, which is probably what you intended.
|
15
|
+
#
|
16
|
+
# The next paragraph contains two phrases separated by a single newline character:
|
17
|
+
#
|
18
|
+
# Roses are red
|
19
|
+
# Violets are blue
|
20
|
+
#
|
21
|
+
# becomes
|
22
|
+
#
|
23
|
+
# Roses are red
|
24
|
+
# Violets are blue
|
25
|
+
#
|
26
|
+
# ### Multiple underscores in words
|
27
|
+
#
|
28
|
+
# It is not reasonable to italicize just part of a word, especially when you're dealing with code
|
29
|
+
# and names often appear with multiple underscores. Therefore, MFM ignores multiple underscores
|
30
|
+
# in words.
|
31
|
+
#
|
32
|
+
# perform_complicated_task
|
33
|
+
# do_this_and_do_that_and_another_thing
|
34
|
+
#
|
35
|
+
# becomes
|
36
|
+
#
|
37
|
+
# perform_complicated_task
|
38
|
+
# do_this_and_do_that_and_another_thing
|
39
|
+
#
|
40
|
+
# @see http://github.github.com/github-flavored-markdown/
|
41
|
+
# @see http://daringfireball.net/projects/markdown/syntax
|
42
|
+
#
|
43
|
+
class FlavoredMarkdown
|
44
|
+
# For maximum flavor, add one shake of seasoning to markdown text **before** cooking with a
|
45
|
+
# Markdown engine.
|
46
|
+
#
|
47
|
+
# @example Cooking with BlueCloth
|
48
|
+
#
|
49
|
+
# BlueCloth.new(Mango::FlavoredMarkdown.shake(text)).to_html
|
50
|
+
#
|
51
|
+
# @param [String] text
|
52
|
+
# @return [String] Seasoned text that is ready to cook!
|
53
|
+
#
|
54
|
+
def self.shake(text)
|
55
|
+
# Extract pre blocks
|
56
|
+
extractions = {}
|
57
|
+
text.gsub!(%r{<pre>.*?</pre>}m) do |match|
|
58
|
+
md5 = Digest::MD5.hexdigest(match)
|
59
|
+
extractions[md5] = match
|
60
|
+
"{gfm-extraction-#{md5}}"
|
61
|
+
end
|
62
|
+
|
63
|
+
# prevent foo_bar_baz from ending up with an italic word in the middle
|
64
|
+
text.gsub!(/(^(?! {4}|\t)\w+_\w+_\w[\w_]*)/) do |x|
|
65
|
+
x.gsub("_", '\_') if x.split('').sort.join[0..1] == "__"
|
66
|
+
end
|
67
|
+
|
68
|
+
# in very clear cases, let newlines become <br /> tags
|
69
|
+
text.gsub!(/^[\w\<][^\n]*\n+/) do |x|
|
70
|
+
x =~ /\n{2}/ ? x : (x.strip!; x << " \n")
|
71
|
+
end
|
72
|
+
|
73
|
+
# Insert pre block extractions
|
74
|
+
text.gsub!(/\{gfm-extraction-([0-9a-f]{32})\}/) do
|
75
|
+
"\n\n" + extractions[$1]
|
76
|
+
end
|
77
|
+
|
78
|
+
text
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module Mango
|
3
|
+
module Rack
|
4
|
+
class Debugger
|
5
|
+
def initialize(app, kernel = Kernel, ruby_version = RUBY_VERSION)
|
6
|
+
@app = app
|
7
|
+
kernel.require "ruby-debug"
|
8
|
+
::Debugger.start
|
9
|
+
puts "=> Debugger enabled"
|
10
|
+
rescue LoadError
|
11
|
+
gem_name = (ruby_version >= "1.9" ? "ruby-debug19" : "ruby-debug")
|
12
|
+
puts "=> Debugger not enabled"
|
13
|
+
puts "=> The #{gem_name} library is required to run the server in debugging mode."
|
14
|
+
puts "=> With RubyGems, use 'gem install #{gem_name}' to install the library."
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
@app.call(env)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/mango/runner.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require "thor"
|
3
|
+
|
4
|
+
module Mango
|
5
|
+
class Runner < Thor
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
add_runtime_options!
|
9
|
+
|
10
|
+
source_root File.join(File.dirname(__FILE__), "templates")
|
11
|
+
|
12
|
+
desc "create /path/to/your/app",
|
13
|
+
"Creates a new Mango application with a default directory structure and configuration at the path you specify."
|
14
|
+
def create(destination)
|
15
|
+
self.destination_root = destination
|
16
|
+
copy_file("config.ru", File.join(self.destination_root, "config.ru"))
|
17
|
+
copy_file("Gemfile", File.join(self.destination_root, "Gemfile"))
|
18
|
+
copy_file("README.md", File.join(self.destination_root, "README.md"))
|
19
|
+
|
20
|
+
build_content_path
|
21
|
+
build_themes_path
|
22
|
+
end
|
23
|
+
|
24
|
+
###############################################################################################
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def build_content_path
|
29
|
+
content_root = File.join(self.destination_root, "content")
|
30
|
+
empty_directory(content_root)
|
31
|
+
copy_file("content/index.md", File.join(content_root, "index.md"))
|
32
|
+
end
|
33
|
+
|
34
|
+
def build_themes_path
|
35
|
+
themes_root = File.join(self.destination_root, "themes")
|
36
|
+
empty_directory(themes_root)
|
37
|
+
|
38
|
+
default_root = File.join(themes_root, "default")
|
39
|
+
empty_directory(default_root)
|
40
|
+
|
41
|
+
build_public_path default_root
|
42
|
+
build_styles_path default_root
|
43
|
+
build_views_path default_root
|
44
|
+
end
|
45
|
+
|
46
|
+
###############################################################################################
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def build_public_path(destination)
|
51
|
+
public_root = File.join(destination, "public")
|
52
|
+
empty_directory(public_root)
|
53
|
+
create_file(File.join(public_root, "favicon.ico"))
|
54
|
+
copy_file("themes/default/public/robots.txt", File.join(public_root, "robots.txt"))
|
55
|
+
|
56
|
+
build_public_images_path public_root
|
57
|
+
build_public_javascripts_path public_root
|
58
|
+
build_public_styles_path public_root
|
59
|
+
end
|
60
|
+
|
61
|
+
def build_public_images_path(destination)
|
62
|
+
public_images_root = File.join(destination, "images")
|
63
|
+
empty_directory(public_images_root)
|
64
|
+
copy_file("themes/default/public/images/particles.gif", File.join(public_images_root, "particles.gif"))
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_public_javascripts_path(destination)
|
68
|
+
public_javascripts_root = File.join(destination, "javascripts")
|
69
|
+
empty_directory(public_javascripts_root)
|
70
|
+
copy_file("themes/default/public/javascripts/fireworks.js", File.join(public_javascripts_root, "fireworks.js"))
|
71
|
+
copy_file("themes/default/public/javascripts/timer.js", File.join(public_javascripts_root, "timer.js"))
|
72
|
+
end
|
73
|
+
|
74
|
+
def build_public_styles_path(destination)
|
75
|
+
public_styles_root = File.join(destination, "styles")
|
76
|
+
empty_directory(public_styles_root)
|
77
|
+
copy_file("themes/default/public/styles/fireworks.css", File.join(public_styles_root, "fireworks.css"))
|
78
|
+
copy_file("themes/default/public/styles/reset.css", File.join(public_styles_root, "reset.css"))
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_styles_path(destination)
|
82
|
+
styles_root = File.join(destination, "styles")
|
83
|
+
empty_directory(styles_root)
|
84
|
+
copy_file("themes/default/styles/screen.sass", File.join(styles_root, "screen.sass"))
|
85
|
+
end
|
86
|
+
|
87
|
+
def build_views_path(destination)
|
88
|
+
views_root = File.join(destination, "views")
|
89
|
+
empty_directory(views_root)
|
90
|
+
copy_file("themes/default/views/404.haml", File.join(views_root, "404.haml"))
|
91
|
+
copy_file("themes/default/views/layout.haml", File.join(views_root, "layout.haml"))
|
92
|
+
copy_file("themes/default/views/page.haml", File.join(views_root, "page.haml"))
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|