mango 0.1.1 → 0.5.0.beta1
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.
- 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
|